前幾天寫了一個2048程序,是基於python3+pygame實現的,對於初學python的同學來說應該是很好的練手項目,現在將源碼分享給大家,添加了清晰的注釋,相信大家能看的很明白
運行效果如下:
游戲結束后的效果如下:
完整代碼如下,如果需要下載素材(圖片、字體等可以到 https://www.itprojects.cn/web/material/details.html?id=10進行下載)
1 import random 2 import sys 3 from collections import Iterable 4 from functools import reduce 5 6 import pygame 7 8 # 屏幕尺寸 9 WIDTH, HEIGHT = (650, 370) 10 # 背景顏色 11 BG_COLOR = '#92877d' 12 # 棋盤需要的數據 13 MARGIN_SIZE = 10 # 間隔大小 14 BLOCK_SIZE = 80 # 棋子位置大小 15 16 17 def draw_tips(screen): 18 """ 19 顯示提示信息 20 """ 21 # 顯示"分數:" 22 tips_img = pygame.image.load("resources/images/tips.png") 23 screen.blit(tips_img, (375, 200)) 24 25 26 def get_score(chess_nums_temp): 27 """ 28 計算當前棋盤的總分數 29 """ 30 31 def sum_all(x, y): 32 if isinstance(x, Iterable): 33 return sum(x) + sum(y) 34 return x + sum(y) 35 36 return reduce(sum_all, chess_nums_temp) 37 38 39 def draw_score(screen, score): 40 """ 41 顯示分數 42 """ 43 # 顯示數字 44 font_size_big = 60 45 font_color = (0, 255, 255) 46 font_big = pygame.font.Font("resources/font/Gabriola.ttf", font_size_big) 47 score = font_big.render(str(score), True, font_color) 48 screen.blit(score, (470, 25)) 49 # 顯示"分數:" 50 score_img = pygame.image.load("resources/images/score.png") 51 screen.blit(score_img, (370, 30)) 52 53 54 def show_game_over(screen): 55 font_size_big = 60 56 font_size_small = 30 57 font_color = (255, 255, 255) 58 font_big = pygame.font.Font("resources/font/Gabriola.ttf", font_size_big) 59 font_small = pygame.font.Font("resources/font/Gabriola.ttf", font_size_small) 60 surface = screen.convert_alpha() 61 surface.fill((127, 255, 212, 2)) 62 text = font_big.render('Game Over!', True, font_color) 63 text_rect = text.get_rect() 64 text_rect.centerx, text_rect.centery = WIDTH / 2, HEIGHT / 2 - 50 65 surface.blit(text, text_rect) 66 button_width, button_height = 100, 40 67 button_start_x_left = WIDTH / 2 - button_width - 20 68 button_start_x_right = WIDTH / 2 + 20 69 button_start_y = HEIGHT / 2 - button_height / 2 + 20 70 pygame.draw.rect(surface, (0, 255, 255), (button_start_x_left, button_start_y, button_width, button_height)) 71 text_restart = font_small.render('Restart', True, font_color) 72 text_restart_rect = text_restart.get_rect() 73 text_restart_rect.centerx, text_restart_rect.centery = button_start_x_left + button_width / 2, button_start_y + button_height / 2 74 surface.blit(text_restart, text_restart_rect) 75 pygame.draw.rect(surface, (0, 255, 255), (button_start_x_right, button_start_y, button_width, button_height)) 76 text_quit = font_small.render('Quit', True, font_color) 77 text_quit_rect = text_quit.get_rect() 78 text_quit_rect.centerx, text_quit_rect.centery = button_start_x_right + button_width / 2, button_start_y + button_height / 2 79 surface.blit(text_quit, text_quit_rect) 80 clock = pygame.time.Clock() 81 while True: 82 screen.blit(surface, (0, 0)) 83 for event in pygame.event.get(): 84 if event.type == pygame.QUIT: 85 pygame.quit() 86 sys.exit() 87 elif event.type == pygame.MOUSEBUTTONDOWN and event.button: 88 if text_quit_rect.collidepoint(pygame.mouse.get_pos()): 89 sys.exit() 90 if text_restart_rect.collidepoint(pygame.mouse.get_pos()): 91 return True 92 pygame.display.update() 93 clock.tick(60) 94 95 96 def judge_game_over(field): 97 """ 98 只要有1個方向可以移動,那么游戲就沒結束 99 """ 100 return not any([judge_move_left(field), judge_move_right(field), judge_move_up(field), judge_move_down(field)]) 101 102 103 def judge_move_up(chess_nums_temp): 104 # 對棋盤的數字進行「行與列轉置」,即原來在第2行第3列變為第3行第2列 105 # zip: 實現 106 # *chess_nums_temp對列表進行拆包 107 chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)] 108 return judge_move_left(chess_nums_temp) 109 110 111 def judge_move_down(chess_nums_temp): 112 """ 113 邏輯:判斷能否向下移動, 也就是對於元素進行轉置, 判斷轉置后的棋盤能否向右移動 114 """ 115 # 1.「行與列轉置」 116 chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)] 117 # 2. 判斷是否可以向右移動 118 return judge_move_right(chess_nums_temp) 119 120 121 def judge_move_left(chess_nums_temp): 122 # 只要棋盤的任意一行可以向左移動, 就返回True 123 for row in chess_nums_temp: 124 for i in range(3): # 每一行判斷3次 125 # 如果判斷的左邊的數為0,右邊的數不為0,則說明可以向左移動; 126 if row[i] == 0 and row[i + 1] != 0: 127 return True 128 elif row[i] != 0 and row[i + 1] == row[i]: 129 # 如果判斷的左邊的數不為0,且左右2個數相等,則說明可以向左移動; 130 return True 131 return False 132 133 134 def judge_move_right(chess_nums_temp): 135 # 對棋盤的每一行元素進行反轉,此時就可以用向左的函數進行判斷了 136 return judge_move_left([row[::-1] for row in chess_nums_temp]) 137 138 139 def move_left(chess_nums_temp): 140 for i, row in enumerate(chess_nums_temp): 141 # 1.把這一行的非0 數字向前放,把0向后放。例如之前是[0, 2, 2, 2]-->[2, 2, 2, 0] 142 row = sorted(row, key=lambda x: 1 if x == 0 else 0) 143 144 # 2.依次循環判斷兩個數是否相等,如果相等 第一個*2 第二個數為0。例如[2, 2, 2, 0]-->[4, 0, 2, 0] 145 for index in range(3): 146 if row[index] == row[index + 1]: 147 row[index] *= 2 148 row[index + 1] = 0 149 150 # 3.將合並之后的空隙移除,即非0靠左,0靠右。例如[4, 0, 2, 0]-->[4, 2, 0, 0] 151 row = sorted(row, key=lambda x: 1 if x == 0 else 0) 152 # 4. 更新數字列表,因為這一行已經是操作之后的了 153 chess_nums_temp[i] = row 154 return chess_nums_temp 155 156 157 def move_right(chess_nums_temp): 158 # 先翻翻轉 159 chess_nums_temp = [row[::-1] for row in chess_nums_temp] 160 # 然后在調用像左移動的功能 161 move_left(chess_nums_temp) 162 # 最后再次翻轉,實現之前的樣子 163 return [row[::-1] for row in chess_nums_temp] 164 165 166 def move_up(chess_nums_temp): 167 # "行與列轉置" 168 chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)] 169 # 向左移動 170 chess_nums_temp = move_left(chess_nums_temp) 171 # 再次"行與列轉置"從而實現復原 172 return [list(row) for row in zip(*chess_nums_temp)] 173 174 175 def move_down(chess_nums_temp): 176 # "行與列轉置" 177 chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)] 178 # 向右移動 179 chess_nums_temp = move_right(chess_nums_temp) 180 # 再次"行與列轉置"從而實現復原 181 return [list(row) for row in zip(*chess_nums_temp)] 182 183 184 def move(chess_nums_temp, direction): 185 """ 186 根據方向移動數字 187 """ 188 # 存儲判斷各個方向是否可移動對應的函數 189 judge_move_func_dict = { 190 'left': judge_move_left, 191 'right': judge_move_right, 192 'up': judge_move_up, 193 'down': judge_move_down 194 } 195 # 存儲各個方向移動的函數 196 move_func_dict = { 197 'left': move_left, 198 'right': move_right, 199 'up': move_up, 200 'down': move_down 201 } 202 203 # 調用對應的函數,判斷是否可以朝這個方向移動 204 ret = judge_move_func_dict[direction](chess_nums_temp) 205 print("%s方向是否可以移動:" % direction, ret) 206 if ret: 207 chess_nums_temp = move_func_dict[direction](chess_nums_temp) 208 create_random_num(chess_nums_temp) 209 210 # 返回列表,如果更新了就是新的,如果沒有更新就是之前的那個 211 return chess_nums_temp 212 213 214 def get_num_color(num): 215 """ 216 根據當前要顯示的數字,提取出背景色以及字體顏色 217 對應的數字:[方格背景顏色, 方格里的字體顏色] 218 """ 219 color_dict = { 220 2: ['#eee4da', '#776e65'], 4: ['#ede0c8', '#776e65'], 8: ['#f2b179', '#f9f6f2'], 221 16: ['#f59563', '#f9f6f2'], 32: ['#f67c5f', '#f9f6f2'], 64: ['#f65e3b', '#f9f6f2'], 222 128: ['#edcf72', '#f9f6f2'], 256: ['#edcc61', '#f9f6f2'], 512: ['#edc850', '#f9f6f2'], 223 1024: ['#edc53f', '#f9f6f2'], 2048: ['#edc22e', '#f9f6f2'], 4096: ['#eee4da', '#776e65'], 224 8192: ['#edc22e', '#f9f6f2'], 16384: ['#f2b179', '#776e65'], 32768: ['#f59563', '#776e65'], 225 65536: ['#f67c5f', '#f9f6f2'], 0: ['#9e948a', None] 226 } 227 return color_dict[num] 228 229 230 def create_random_num(nums_temp): 231 """ 232 在棋盤中隨機生成一個數字 233 """ 234 # 存儲所有空位置 235 positions = list() 236 for row, line in enumerate(nums_temp): 237 for col, num in enumerate(line): 238 if num == 0: 239 positions.append((row, col)) 240 241 # 隨機從空位置列表中抽取一個,然后拆包 242 row, col = random.choice(positions) 243 nums_temp[row][col] = random.choice([2, 4, 2]) # 隨機從2個2,1個4中抽取,這樣抽到2的概率是4的2倍 244 245 246 def draw_nums(screen, chess_nums_temp): 247 """ 248 顯示棋盤上的數字 249 """ 250 # 准備字體等 251 font_size = BLOCK_SIZE - 10 252 font = pygame.font.Font("./resources/font/Gabriola.ttf", font_size) 253 # 遍歷數字 254 for i, line in enumerate(chess_nums_temp): 255 for j, num in enumerate(line): 256 if num != 0: 257 # 計算顯示位置(x坐標、y坐標) 258 x = MARGIN_SIZE * (j + 1) + BLOCK_SIZE * j 259 y = MARGIN_SIZE * (i + 1) + BLOCK_SIZE * i 260 # 獲取顏色 261 font_color = pygame.Color(get_num_color(num)[1]) 262 # 顯示數字 263 text = font.render(str(num), True, font_color) 264 text_rect = text.get_rect() 265 text_rect.centerx, text_rect.centery = x + BLOCK_SIZE / 2, y + BLOCK_SIZE / 2 266 # 用對應的數字背景色,重新繪制這個方塊 267 pygame.draw.rect(screen, pygame.Color(get_num_color(num)[0]), (x, y, BLOCK_SIZE, BLOCK_SIZE)) 268 screen.blit(text, text_rect) 269 270 271 def draw_chess_board(screen): 272 """ 273 顯示棋盤 274 """ 275 for i in range(4): 276 for j in range(4): 277 x = MARGIN_SIZE * (j + 1) + BLOCK_SIZE * j 278 y = MARGIN_SIZE * (i + 1) + BLOCK_SIZE * i 279 pygame.draw.rect(screen, pygame.Color('#f9f6f2'), (x, y, BLOCK_SIZE, BLOCK_SIZE)) 280 281 282 def run(screen): 283 # 定義列表,用來記錄當前棋盤上的所有數字,如果某位置沒有數字,則為0 284 chess_nums = [[0 for _ in range(4)] for _ in range(4)] 285 # 隨機生成一個數字 286 create_random_num(chess_nums) 287 create_random_num(chess_nums) 288 # 記錄當前的分數 289 score = get_score(chess_nums) 290 # 創建計時器(防止while循環過快,占用太多CPU的問題) 291 clock = pygame.time.Clock() 292 while True: 293 # 事件檢測(鼠標點擊、鍵盤按下等) 294 for event in pygame.event.get(): 295 if event.type == pygame.QUIT: 296 pygame.quit() 297 sys.exit() 298 elif event.type == pygame.KEYDOWN: 299 if event.key in [pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT]: 300 direction = {pygame.K_UP: 'up', pygame.K_DOWN: 'down', pygame.K_LEFT: 'left', pygame.K_RIGHT: 'right'}[event.key] 301 print("按下了方向鍵:", direction) 302 chess_nums = move(chess_nums, direction) 303 if judge_game_over(chess_nums): 304 print("游戲結束....") 305 return 306 # 每按下方向鍵,就重新計算 307 score = get_score(chess_nums) 308 309 # 顯示背景色 310 screen.fill(pygame.Color(BG_COLOR)) 311 312 # 顯示棋盤 313 draw_chess_board(screen) 314 315 # 顯示棋盤上的數字 316 draw_nums(screen, chess_nums) 317 318 # 顯示分數 319 draw_score(screen, score) 320 321 # 顯示操作提示 322 draw_tips(screen) 323 324 # 刷新顯示(此時窗口才會真正的顯示) 325 pygame.display.update() 326 327 # FPS(每秒鍾顯示畫面的次數) 328 clock.tick(60) # 通過一定的延時,實現1秒鍾能夠循環60次 329 330 331 def main(): 332 # 游戲初始化 333 pygame.init() 334 screen = pygame.display.set_mode((WIDTH, HEIGHT)) 335 while True: 336 # 運行一次游戲 337 run(screen) 338 # 顯示游戲結束,是否重來 339 show_game_over(screen) 340 341 342 if __name__ == '__main__': 343 main()