Learn how to code a 2048 game in Python with Tkinter. Follow our step-by-step tutorial for a fun and educational game development experience.
In the realm of programming and game development, Python stands out as a versatile and accessible language. One of the fascinating projects you can embark on is creating your own version of the popular 2048 game. This article will guide you through the process, step by step, from setting up your development environment to coding the game logic, all the way to adding a graphical user interface (GUI) using Tkinter.
Before we delve into the world of game development, you need to ensure your Python environment is ready for action. Here's what you should do:
If you haven't already, download and install Python from the official website (https://www.python.org/downloads/). Make sure to add Python to your system's PATH during installation.
Tkinter is Python's standard GUI library and comes bundled with Python. There's no need to install it separately.
Create a dedicated folder for your 2048 game project and open it in your favorite code editor or integrated development environment (IDE).
Create 03 Python (.py) files, such as constants.py, logic.py, and main.py.
Define various constants and color dictionaries for a 2048 game
SIZE = 400 GRID_LEN = 4 GRID_PADDING = 10 BACKGROUND_COLOR_GAME = "#92877d" BACKGROUND_COLOR_CELL_EMPTY = "#9e948a" BACKGROUND_COLOR_DICT = { 2: "#eee4da", 4: "#ede0c8", 8: "#f2b179", 16: "#f59563", 32: "#f67c5f", 64: "#f65e3b", 128: "#edcf72", 256: "#edcc61", 512: "#edc850", 1024: "#edc53f", 2048: "#edc22e", 4096: "#eee4da", 8192: "#edc22e", 16384: "#f2b179", 32768: "#f59563", 65536: "#f67c5f", } CELL_COLOR_DICT = { 2: "#776e65", 4: "#776e65", 8: "#f9f6f2", 16: "#f9f6f2", 32: "#f9f6f2", 64: "#f9f6f2", 128: "#f9f6f2", 256: "#f9f6f2", 512: "#f9f6f2", 1024: "#f9f6f2", 2048: "#f9f6f2", 4096: "#776e65", 8192: "#f9f6f2", 16384: "#776e65", 32768: "#776e65", 65536: "#f9f6f2", } FONT = ("Verdana",40,"bold") KEY_QUIT = "Escape" KEY_BACK = "b" KEY_UP = "Up" KEY_DOWN = "Down" KEY_LEFT = "Left" KEY_RIGHT = "Right" KEY_UP_ALT1 = "w" KEY_DOWN_ALT1 = "s" KEY_LEFT_ALT1 = "a" KEY_RIGHT_ALT1 = "d" KEY_UP_ALT2 = "i" KEY_DOWN_ALT2 = "k" KEY_LEFT_ALT2 = "j" KEY_RIGHT_ALT2 = "l"
# # CS1010FC --- Programming Methodology # # Mission N Solutions # # Note that written answers are commented out to allow us to run your # code easily while grading your problem set. import random import constant as c ####### # Task 1a # ####### # [Marking Scheme] # Points to note: # Matrix elements must be equal but not identical # 1 mark for creating the correct matrix def new_game(n): matrix = [] for i in range(n): matrix.append([0] * n) matrix = add_two(matrix) matrix = add_two(matrix) return matrix ########### # Task 1b # ########### # [Marking Scheme] # Points to note: # Must ensure that it is created on a zero entry # 1 mark for creating the correct loop def add_two(mat): a = random.randint(0, len(mat)-1) b = random.randint(0, len(mat)-1) while mat[a][b] != 0: a = random.randint(0, len(mat)-1) b = random.randint(0, len(mat)-1) mat[a][b] = 2 return mat ########### # Task 1c # ########### # [Marking Scheme] # Points to note: # Matrix elements must be equal but not identical # 0 marks for completely wrong solutions # 1 mark for getting only one condition correct # 2 marks for getting two of the three conditions # 3 marks for correct checking def game_state(mat): # check for win cell for i in range(len(mat)): for j in range(len(mat[0])): if mat[i][j] == 2048: return 'win' # check for any zero entries for i in range(len(mat)): for j in range(len(mat[0])): if mat[i][j] == 0: return 'not over' # check for same cells that touch each other for i in range(len(mat)-1): # intentionally reduced to check the row on the right and below # more elegant to use exceptions but most likely this will be their solution for j in range(len(mat[0])-1): if mat[i][j] == mat[i+1][j] or mat[i][j+1] == mat[i][j]: return 'not over' for k in range(len(mat)-1): # to check the left/right entries on the last row if mat[len(mat)-1][k] == mat[len(mat)-1][k+1]: return 'not over' for j in range(len(mat)-1): # check up/down entries on last column if mat[j][len(mat)-1] == mat[j+1][len(mat)-1]: return 'not over' return 'lose' ########### # Task 2a # ########### # [Marking Scheme] # Points to note: # 0 marks for completely incorrect solutions # 1 mark for solutions that show general understanding # 2 marks for correct solutions that work for all sizes of matrices def reverse(mat): new = [] for i in range(len(mat)): new.append([]) for j in range(len(mat[0])): new[i].append(mat[i][len(mat[0])-j-1]) return new ########### # Task 2b # ########### # [Marking Scheme] # Points to note: # 0 marks for completely incorrect solutions # 1 mark for solutions that show general understanding # 2 marks for correct solutions that work for all sizes of matrices def transpose(mat): new = [] for i in range(len(mat[0])): new.append([]) for j in range(len(mat)): new[i].append(mat[j][i]) return new ########## # Task 3 # ########## # [Marking Scheme] # Points to note: # The way to do movement is compress -> merge -> compress again # Basically if they can solve one side, and use transpose and reverse correctly they should # be able to solve the entire thing just by flipping the matrix around # No idea how to grade this one at the moment. I have it pegged to 8 (which gives you like, # 2 per up/down/left/right?) But if you get one correct likely to get all correct so... # Check the down one. Reverse/transpose if ordered wrongly will give you wrong result. def cover_up(mat): new = [] for j in range(c.GRID_LEN): partial_new = [] for i in range(c.GRID_LEN): partial_new.append(0) new.append(partial_new) done = False for i in range(c.GRID_LEN): count = 0 for j in range(c.GRID_LEN): if mat[i][j] != 0: new[i][count] = mat[i][j] if j != count: done = True count += 1 return new, done def merge(mat, done): for i in range(c.GRID_LEN): for j in range(c.GRID_LEN-1): if mat[i][j] == mat[i][j+1] and mat[i][j] != 0: mat[i][j] *= 2 mat[i][j+1] = 0 done = True return mat, done def up(game): print("up") # return matrix after shifting up game = transpose(game) game, done = cover_up(game) game, done = merge(game, done) game = cover_up(game)[0] game = transpose(game) return game, done def down(game): print("down") # return matrix after shifting down game = reverse(transpose(game)) game, done = cover_up(game) game, done = merge(game, done) game = cover_up(game)[0] game = transpose(reverse(game)) return game, done def left(game): print("left") # return matrix after shifting left game, done = cover_up(game) game, done = merge(game, done) game = cover_up(game)[0] return game, done def right(game): print("right") # return matrix after shifting right game = reverse(game) game, done = cover_up(game) game, done = merge(game, done) game = cover_up(game)[0] game = reverse(game) return game, done
from tkinter import Frame, Label, CENTER import random import logic import constant as c def gen(): return random.randint(0, c.GRID_LEN - 1) class GameGrid(Frame): def __init__(self): Frame.__init__(self) self.grid() self.master.title('2048') self.master.bind("", self.key_down) self.commands = { c.KEY_UP: logic.up, c.KEY_DOWN: logic.down, c.KEY_LEFT: logic.left, c.KEY_RIGHT: logic.right, c.KEY_UP_ALT1: logic.up, c.KEY_DOWN_ALT1: logic.down, c.KEY_LEFT_ALT1: logic.left, c.KEY_RIGHT_ALT1: logic.right, c.KEY_UP_ALT2: logic.up, c.KEY_DOWN_ALT2: logic.down, c.KEY_LEFT_ALT2: logic.left, c.KEY_RIGHT_ALT2: logic.right, } self.grid_cells = [] self.init_grid() self.matrix = logic.new_game(c.GRID_LEN) self.history_matrixs = [] self.update_grid_cells() self.mainloop() def init_grid(self): background = Frame(self, bg=c.BACKGROUND_COLOR_GAME,width=c.SIZE, height=c.SIZE) background.grid() for i in range(c.GRID_LEN): grid_row = [] for j in range(c.GRID_LEN): cell = Frame( background, bg=c.BACKGROUND_COLOR_CELL_EMPTY, width=c.SIZE / c.GRID_LEN, height=c.SIZE / c.GRID_LEN ) cell.grid( row=i, column=j, padx=c.GRID_PADDING, pady=c.GRID_PADDING ) t = Label( master=cell, text="", bg=c.BACKGROUND_COLOR_CELL_EMPTY, justify=CENTER, font=c.FONT, width=5, height=2) t.grid() grid_row.append(t) self.grid_cells.append(grid_row) def update_grid_cells(self): for i in range(c.GRID_LEN): for j in range(c.GRID_LEN): new_number = self.matrix[i][j] if new_number == 0: self.grid_cells[i][j].configure(text="",bg=c.BACKGROUND_COLOR_CELL_EMPTY) else: self.grid_cells[i][j].configure( text=str(new_number), bg=c.BACKGROUND_COLOR_DICT[new_number], fg=c.CELL_COLOR_DICT[new_number] ) self.update_idletasks() def key_down(self, event): key = event.keysym print(event) if key == c.KEY_QUIT: exit() if key == c.KEY_BACK and len(self.history_matrixs) > 1: self.matrix = self.history_matrixs.pop() self.update_grid_cells() print('back on step total step:', len(self.history_matrixs)) elif key in self.commands: self.matrix, done = self.commands[key](self.matrix) if done: self.matrix = logic.add_two(self.matrix) # record last move self.history_matrixs.append(self.matrix) self.update_grid_cells() if logic.game_state(self.matrix) == 'win': self.grid_cells[1][1].configure(text="You", bg=c.BACKGROUND_COLOR_CELL_EMPTY) self.grid_cells[1][2].configure(text="Win!", bg=c.BACKGROUND_COLOR_CELL_EMPTY) if logic.game_state(self.matrix) == 'lose': self.grid_cells[1][1].configure(text="You", bg=c.BACKGROUND_COLOR_CELL_EMPTY) self.grid_cells[1][2].configure(text="Lose!", bg=c.BACKGROUND_COLOR_CELL_EMPTY) def generate_next(self): index = (gen(), gen()) while self.matrix[index[0]][index[1]] != 0: index = (gen(), gen()) self.matrix[index[0]][index[1]] = 2 game_grid = GameGrid()
i. SIZE, GRID_LEN, and GRID_PADDING: These constants define the size of the game board and grid. SIZE is set to 400, which likely represents the pixel size of the game window. GRID_LEN is set to 4, indicating that the game grid is a 4x4 grid. GRID_PADDING is set to 10, representing the padding (empty space) between grid cells.
ii. BACKGROUND_COLOR_GAME and BACKGROUND_COLOR_CELL_EMPTY: These constants define the background colors for the game board and empty grid cells, respectively. They are represented as hexadecimal color codes.
iii. BACKGROUND_COLOR_DICT and CELL_COLOR_DICT: These dictionaries define background colors and text colors for different values (powers of 2) that can appear on the grid. For example, the key-value pairs in BACKGROUND_COLOR_DICT associate a color with each possible tile value, such as 2, 4, 8, 16, and so on. Similarly, CELL_COLOR_DICT associates text colors with tile values.
iv. FONT: This constant specifies the font properties to be used for displaying text on the grid. It appears to use the "Verdana" font with a size of 40 and a bold style.
v. KEY_QUIT, KEY_BACK, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_UP_ALT1, KEY_DOWN_ALT1, KEY_LEFT_ALT1, KEY_RIGHT_ALT1, KEY_UP_ALT2, KEY_DOWN_ALT2, KEY_LEFT_ALT2, KEY_RIGHT_ALT2: These constants define keybindings for controlling the game. For example, "Up" corresponds to the up arrow key, "Down" corresponds to the down arrow key, and so on. These keybindings are used to move tiles in the game or perform other actions.
The game_state function takes a matrix mat as input.
It checks three conditions to determine the game state:
Depending on the conditions met, it returns 'win', 'not over', or 'lose' as the game state.
Four functions (up, down, left, and right) are defined to handle game moves in different directions.
Each function represents a move in the game:
Each function performs the following steps:
Each function returns the updated matrix after the move and a boolean done indicating whether any tiles were moved or merged during the move.
Import Statements:
gen() Function:
GameGrid Class:
Initializing the Game Grid (init_grid Method):
update_grid_cells Method:
key_down Method:
This method handles key events when a key is pressed during the game.
It checks the pressed key (event.keysym) and performs the following actions:
generate_next Method:
Creating an Instance of GameGrid:
Congratulations! You've successfully created a 2048 game in Python with Tkinter. You've learned game development, GUI design, and logic implementation. Feel free to customize your game further and share it with others.
Now, go ahead and enjoy your newly developed game! If you have any questions or need assistance, feel free to ask in the comments below. Happy coding!
Code by: Yangshun Tay
Absolutely! You can personalize the game's look by modifying the GUI elements and adding your graphics.
Yes, you can set a different winning condition by modifying the game logic code.
Indeed, you can create a larger grid by changing the dimensions of the game board.
There are many online tutorials and documentation available to help you master Python and Tkinter. Websites like W3Schools and Python.org are great places to start.
You can explore various game development courses and forums, such as GitHub, to discover and contribute to open-source game projects.
That’s a wrap!
I hope you enjoyed this article
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee.
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊