笔者作为python小白,2048小游戏无疑是非常适合笔者的练手小项目,因此花了两天时间用python复现了2048小游戏。本文主要总结一下笔者做2048的一点经验,提供关于实现2048小游戏的资料,如果有如同笔者这样的小白的后来者,希望本文能给你一些帮助。
资料方面,参考过蓝桥云课的“Python200行代码实现2048”(链接:https://www.lanqiao.cn/courses/368),但蓝桥云课的2048主要采用curses终端图形编程库编程,画面比较简陋,仅由点、线和数字组成,因此只参考了蓝桥云课的算法,在此基础上,笔者用pygame库进行编程,优化了游戏画面(pygame库的学习参考《python从入门到实践》这本书的“外星人大战”项目)。
笔者认为2048的难点只有两个,一是棋盘操作算法,即棋盘内的数字跟随方向键移动、合并,这个算法我参考了蓝桥云课的算法,但也仅仅是参考,并没有套用,我用了numpy库的ndarray数组和zeros函数,生成4x4的棋盘模,通过修改、添加和删减数组内的0来达到2048游戏的效果,顺便添加了一个颜色字典color_dict和精灵(Sprite)组来达到不同颜色配不同数字的效果,而把0的颜色设置成背景色就能“去除0”(这是我的偷懒做法)。
第二个难点是判定gameover,即游戏结束时程序不会崩溃,而会跳出gameover。这个算法笔者基本照搬了蓝桥云课的算法,只做了稍稍的修改,核心是使用any()函数返回bool值来判定游戏是否结束。
笔者初学python,经验不足,虽然主要逻辑比较清晰,但代码块显的有点乱,代码也有点冗杂,有想码2048的小伙伴在码前最好整理一下代码块,笔者码到最后悔不当初,恨自己在最开始没有创建一个控制游戏状态game_stats的类,导致在最后码控制游戏状态的代码时,一股脑塞给了其中一个类,看着有点乱。
蓝桥云课的2048:
笔者的2048:
话不多说,接下来直接上代码,整个的主题逻辑代码:
import pygame import sys from settings import Settings from pygame.sprite import Group import function from number_action import Number from scoreboard import Scoreboard from gameover_board import Gameover_board from board import Board from win_board import Win_board def run_game(): pygame.init() screen_set = Settings() screen = pygame.display.set_mode((screen_set.length,screen_set.width)) pygame.display.set_caption("2048") rectangles = Group() gameover = Gameover_board(screen,screen_set) board = Board(screen,screen_set) scoreboard = Scoreboard(screen_set,screen) win = Win_board(screen,screen_set) number = Number(scoreboard,screen_set,win,board) function.creat_fleet(rectangles, screen, screen_set, number) while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE and board.keyspace_active: board.game_active = True board.status = False if event.key == pygame.K_r: number.reset(scoreboard, screen_set,win,board) if event.key == pygame.K_q: sys.exit() elif board.game_active: left,right,up,down = function.check_movable(number) if event.key == pygame.K_a: if any(left): function.update_left(number, scoreboard, screen_set) if event.key == pygame.K_d: if any(right): function.update_right(number, scoreboard, screen_set) if event.key == pygame.K_w: if any(up): function.update_up(number, scoreboard, screen_set) if event.key == pygame.K_s: if any(down): function.update_down(number, scoreboard, screen_set) rectangles.empty() function.creat_fleet(rectangles, screen, screen_set, number) screen.fill(screen_set.bg_color) function.draw_rectangles(rectangles,number) scoreboard.draw_scoreboard() function.show_board(board) function.show_win(win) function.check_win(number, win, board) function.check_gameover(number, gameover) pygame.display.flip() run_game()
function:
from pygame.sprite import Sprite import numpy as np from square import Square def creat_fleet(rectangles,screen,screen_set,number): for i in range(4): for j in range(4): rectangle = Square(screen) rectangle.square_color = screen_set.color_dict[number.init_number[i*4+j]] rectangle.rect.left += j*60 rectangle.rect.top += i*60 rectangle.point_list = [(rectangle.rect.left, rectangle.rect.top), (rectangle.rect.right, rectangle.rect.top), (rectangle.rect.right, rectangle.rect.bottom), (rectangle.rect.left, rectangle.rect.bottom), (rectangle.rect.left, rectangle.rect.top)] rectangles.add(rectangle) def draw_rectangles(rectangles,number): i= 0 for rectangle in rectangles: rectangle.prep_number(str(int(number.init_number[i]))) rectangle.draw_square() i+=1 def update_left(number,scoreboard,screen_set): update_row_left(number) merge_left(number, scoreboard, screen_set) update_row_left(number) number.spawn() def update_right(number,scoreboard,screen_set): invert(number) update_left(number, scoreboard, screen_set) invert(number) def update_up(number,scoreboard,screen_set): transpose(number) update_left(number, scoreboard, screen_set) transpose(number) def update_down(number,scoreboard,screen_set): transpose(number) update_right(number, scoreboard, screen_set) transpose(number) def update_row_left(number): change_number = number.init_number.reshape(4,4) new_number = [] for row in change_number: new_row = [i for i in row if i != 0] for i in range(4 - len(new_row)): new_row.append(0) new_number.append(new_row) number.init_number = np.array(new_number).reshape(16) def merge_left(number,scoreboard,screen_set): change_number = number.init_number.reshape(4,4) new_number =[] pair = False for row in change_number: for i in range(len(row)): if pair: new_number.append(2*row[i]) pair = False else: if i+1<len(row) and row[i]==row[i+1]: pair=True scoreboard.score+= 2*row[i] scoreboard.prep_score(screen_set) new_number.append(0) else: new_number.append(row[i]) number.init_number = np.array(new_number).reshape(16) def invert(number): change_number = number.init_number.reshape(4,4) new_number = [row[::-1] for row in change_number] number.init_number = np.array(new_number).reshape(16) def transpose(number): change_number = number.init_number.reshape(4,4) new_number = np.array(change_number).transpose() number.init_number = new_number.reshape(16) def check_left_movable(number,new_number): change_number = number.init_number.reshape(4, 4) for row in change_number: for i in range(len(row)): if row[i]==0 or (i + 1 < len(row) and row[i] == row[i + 1]): new_number.append(1) def check_right_movable(number,new_number): invert(number) check_left_movable(number,new_number) invert(number) def check_up_movable(number,new_number): transpose(number) check_left_movable(number,new_number) transpose(number) def check_down_movable(number,new_number): transpose(number) check_right_movable(number,new_number) transpose(number) def check_movable(number): new_number1,new_number2,new_number3,new_number4 = [],[],[],[] check_left_movable(number, new_number1) check_right_movable(number, new_number2) check_up_movable(number, new_number3) check_down_movable(number, new_number4) return new_number1,new_number2,new_number3,new_number4 def check_gameover(number,gameover): if not any(check_movable(number)): gameover.draw_gameover() def show_board(board): if board.status: board.draw_board() def check_win(number,win,board): for i in number.init_number: if i >=2048: win.status = True board.game_active = False board.keyspace_active = False def show_win(win): if win.status: win.draw_win()
number_action:
import random import numpy as np class Number(): def __init__(self,scoreboard,screen_set,win,board): self.reset(scoreboard,screen_set,win,board) def spawn(self): self.new_element = 4 if random.randint(0, 100) > 80 else 2 i = random.choice([i for i in range(16) if self.init_number[i]==0]) self.init_number[i] = self.new_element def reset(self,scoreboard,screen_set,win,board): scoreboard.score = 0 scoreboard.prep_score(screen_set) self.init_number = np.zeros(16) self.spawn() self.spawn() win.status = False board.game_active = True board.keyspace_active = True
settings:
class Settings(): def __init__(self): self.width = 400 self.length = 600 self.bg_color = (230,230,230) self.color_dict = {0: (255, 255, 255), 2: (208, 193, 198), 4: (234, 208, 209), 8: (177, 122, 125), 16: (150, 164, 139), 32: (134, 150, 167), 64: (122, 114, 129), 128: (160, 106, 80), 256: (149, 88, 57), 512: (150, 84, 84), 1024: (144, 59, 28), 2048: (81, 31, 30)}
square:
import pygame from pygame.sprite import Sprite class Square(Sprite): def __init__(self,screen): super().__init__() self.screen = screen self.screen_rect = self.screen.get_rect() self.length,self.width = 60,60 self.square_color = (230,230,230) self.text_color = (255, 255, 255) self.rect = pygame.Rect(0,0,self.length,self.width) self.font_1 = pygame.font.SysFont(None,48) self.font_2 = pygame.font.SysFont(None,35) self.rect.left = (self.screen_rect.right-4*60)/2 self.rect.top = (self.screen_rect.bottom-4*60)/2 self.point_list = [(self.rect.left,self.rect.top), (self.rect.right,self.rect.top), (self.rect.right,self.rect.bottom), (self.rect.left,self.rect.bottom), (self.rect.left,self.rect.top)] def draw_square(self): pygame.draw.rect(self.screen,self.square_color,self.rect) pygame.draw.aalines(self.screen,(0,0,0),True,self.point_list,blend=1) self.screen.blit(self.image,self.image_rect) def prep_number(self,number): if int(number)<1024: self.image = self.font_1.render(number,True,self.text_color,self.square_color) else: self.image = self.font_2.render(number,True,self.text_color,self.square_color) self.image_rect = self.image.get_rect() self.image_rect.center = self.rect.center
scoreboard:
import pygame class Scoreboard(): def __init__(self,screen_set,screen): self.screen = screen self.screen_rect = self.screen.get_rect() self.score = 0 self.font = pygame.font.SysFont(None,45) self.score_color = (0,0,0) self.prep_score(screen_set) def prep_score(self,screen_set): self.image = self.font.render("Score:"+str(int(self.score)),True,self.score_color,screen_set.bg_color) self.imagereset = self.font.render("R:RESET", True, self.score_color, screen_set.bg_color) self.imagereset_rect = self.imagereset.get_rect() self.image_rect = self.image.get_rect() self.image_rect.top,self.image_rect.left = self.screen_rect.top,self.screen_rect.left self.imagereset_rect.top, self.imagereset_rect.left = self.screen_rect.top+45, self.screen_rect.left def draw_scoreboard(self): self.screen.blit(self.image,self.image_rect) self.screen.blit(self.imagereset, self.imagereset_rect)
win_board:
import pygame class Win_board(): def __init__(self,screen,screen_set): self.screen = screen self.screen_rect = self.screen.get_rect() self.color = (0,0,0) self.font = pygame.font.SysFont(None,60) self.prep_win(screen_set) self.status = False def prep_win(self,screen_set): self.image = self.font.render("YOU WIN!",True,self.color,screen_set.bg_color) self.image_rect = self.image.get_rect() self.image_rect.centerx = self.screen_rect.centerx self.image_rect.y = self.screen_rect.bottom - 60 def draw_win(self): self.screen.blit(self.image,self.image_rect)
board:
import pygame class Board(): def __init__(self,screen,screen_set): self.screen = screen self.screen_rect = self.screen.get_rect() self.color = (0,0,0) self.font = pygame.font.SysFont(None,45) self.prep_board(screen_set) self.status = True self.game_active = False self.keyspace_active = True def prep_board(self,screen_set): self.image = self.font.render("SPACE:START",True,self.color,screen_set.bg_color) self.image_rect = self.image.get_rect() self.image_rect.centerx = self.screen_rect.centerx self.image_rect.y = self.screen_rect.bottom - 60 def draw_board(self): self.screen.blit(self.image,self.image_rect)
gameover_board:
import pygame class Gameover_board(): def __init__(self,screen,screen_set): self.screen = screen self.screen_rect = self.screen.get_rect() self.color = (0,0,0) self.font = pygame.font.SysFont(None,60) self.prep_gameover(screen_set) def prep_gameover(self,screen_set): self.image = self.font.render("GAME OVER!",True,self.color,screen_set.bg_color) self.image_rect = self.image.get_rect() self.image_rect.centerx = self.screen_rect.centerx self.image_rect.y = self.screen_rect.bottom - 60 def draw_gameover(self): self.screen.blit(self.image,self.image_rect)