我在之前的博客用python实现2048 中,实现了一些基本的2048游戏功能。但在博客发布之后,收到部分博友留言称游戏在运行时出现了一些问题,同时为了实现在之前博客中说游戏小道具,因此本文对游戏进行了全面优化升级,增强了代码的拓展性和可读性,完整资源包请移步至我的资源,或百度云网盘:2048 提取码:e71o
首先此游戏运行需要有redis服务器支持,还没有配置好redis服务器的博友请先移步配置教程。在游戏启动前,先打开服务器程序redis-server.exe,启动好之后如下图所示:
在未启动的情况下,运行python程序将出现如下错误:redis.exceptions.ConnectionError: Error 10061 connecting to 127.0.0.1:6379. 由于目标计算机积极拒绝,无法连接
整个游戏的最终运行效果图如上所示,相较于之前那篇博客,本文配置了两个按钮能够实现更多的游戏交互。同时在游戏界面的设计上参照了部分网络游戏,让整体的界面的更加美观和规整。以及新增自动保存游戏历史数据功能,无论什么时候打开游戏都能从上次的游戏历史继续。
点击菜单后的游戏界面如下:
可供选择的功能分别为继续游戏:点击后将继续游戏;合并全部:将当前能够合并的分数块尽可能的合并;重新开始:点击后将重新开始新的游戏;排行榜:点击后可以查看15条最高得分数据。
排行榜界面如下:
运行游戏需要的全部文件如上,双击start.py运行即可。文件说明如下:
control.py | 游戏控制器,用于控制游戏界面的刷新 |
layers.py | 绘图程序,用于绘制整个游戏界面 |
player.py | 交互控制器,用于产生和控制运行需要的交互逻辑 |
score.py | 得分控制器 |
settings.py | 配置文件,包含游戏所有的运行参数 |
start.py | 主程序,用于启动游戏 |
utility.py | 工具类,包含各类功能模块 |
resources | 游戏资源 |
部分代码展示:
settings.py部分代码如下:
import pygame as py
import numpy as np
import sys, random, time, redis, os
# screen & game
GAME_MODE = 4 # 当前游戏模式为4x4
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 600
BG_COLOR = (251, 248, 240)
TILE_SIZE = 64
FPS = 50
CAPTION = '2048'
# redis
IP = '127.0.0.1'
PASSWORD = None
HASH_NAME = '2048'
RECORD_NAME = 'history'
R = redis.Redis(host=IP, password=PASSWORD, port=6379, db=2, decode_responses=True)
# FONT INIT
FONT_SIZES = {
'times_logo': 100,
'times': 30,
'timesi': 25,
'comici': 30,
'score': 60,
'GameOver': 90,
'menu_button': 30,
'menu_buttons': 40,
'list': 25,
'list_name':45,
}
通过将所有游戏需要的参数统一放置与一个程序内将大大优化代码的可读性,同时也方便了对代码进行修改,想改变游戏整体界面设计的博友可以随意修改参数(笑)。注意:IMAGES_PATH下的路径要做修改
layers.py部分代码如下:
from mygame.game_2048.settings import *
from mygame.game_2048.score import Score
from mygame.game_2048.utility import *
class Layers(object):
def __init__(self, screen):
self.scoreBg = py.image.load(IMAGES_PATH['scoreBg']).convert_alpha()
self.button = py.image.load(IMAGES_PATH['button']).convert_alpha()
self.scoreBg = py.transform.smoothscale(self.scoreBg, IMAGES_SIZE['Bg1'])
self.list = py.image.load(IMAGES_PATH['list']).convert_alpha()
self.menu_buttons = py.image.load(IMAGES_PATH['menu_buttons']).convert_alpha()
self.scoreBg_rect = self.scoreBg.get_rect()
self.score = Score()
self.screen = screen
self.l_scores = self.score.l_scores
def update(self, l_scores):
self.l_scores = l_scores
def draw_logo(self):
logo = fontTimes.render('2048', True, FONT_COLOR['times_logo'])
logo_rect = logo.get_rect()
logo_rect.center = LAYERS_POSITIONS['logo']
self.screen.blit(logo, logo_rect)
def draw_menu_button(self):
self.button = py.transform.smoothscale(self.button, IMAGES_SIZE['menu_button'])
button_rect = self.button.get_rect()
button_rect.center = LAYERS_POSITIONS['menu_button']
self.screen.blit(self.button, button_rect)
text = fontMenu.render('菜 单', True, FONT_COLOR['menu_button'])
text_rect = text.get_rect()
text_rect.center = button_rect.center
self.screen.blit(text, text_rect)
def draw_menu(self):
self.draw_bg()
s_buttons = ['continue_button', 'merge_button', 'restart_button', 'list_button']
s_text = ['继 续 游 戏', '合 并 全 部', '重 新 开 始', '排 行 榜']
gen_button(self.screen, s_buttons, s_text, self.menu_buttons)
该文件内的大部分函数都用于绘制游戏界面或更新游戏图层。
utility.py部分代码如下:
from mygame.game_2048.settings import *
# 获取历史最高分
def get_best_score():
R.hset(HASH_NAME, '0', 0)
scores = list(R.hgetall(HASH_NAME).values())
best_score = max([eval(i) for i in scores])
return best_score
# 获取历史数据
def get_history_scores():
score = 2
number = 0
l_scores = [0] * (pow(GAME_MODE, 2) - 1) + [2]
if R.exists(RECORD_NAME) > 0:
l_scores = eval(R.hget(RECORD_NAME, R.hkeys(RECORD_NAME)[-1]))
score = sum(l_scores)
number = len(R.hgetall(RECORD_NAME)) - 1
return l_scores, score, number
# 获取分数背景图像
def get_image_dict():
image_dict = {}
image_filenames = [i for i in os.listdir(IMAGES_PATH['scores'])]
width = IMAGES_SIZE['score'][0]
height = IMAGES_SIZE['score'][1]
for name in image_filenames:
image = py.transform.smoothscale(py.image.load(IMAGES_PATH['scores'] + name).convert_alpha(),
(width, height))
image_dict[name.replace('.png', '')] = image
return image_dict
# 生成分数图像坐标
def gen_score_pos():
pos = []
coord = []
for inx in range(pow(GAME_MODE, 2)):
a, b = divmod(inx, GAME_MODE)
x = LAYERS_POSITIONS['bg2'][0] + (IMAGES_GAP['scoreImg_gap'] + IMAGES_SIZE['score'][0]) * b + IMAGES_GAP[
'scoreImg_gap']
y = LAYERS_POSITIONS['bg2'][1] + (IMAGES_GAP['scoreImg_gap'] + IMAGES_SIZE['score'][1]) * a + IMAGES_GAP[
'scoreImg_gap']
pos.append((x, y))
coord.append(py.math.Vector2(a, b))
return pos, coord
# 批量生产按钮
def gen_button(surface, s_buttons, s_text, button_img,font=fontMenus):
for inx, button in enumerate(s_buttons):
locals()['board_{}'.format(button)] = py.transform.smoothscale(button_img, IMAGES_SIZE['menu_buttons'])
locals()['rect_{}'.format(button)] = eval('board_{}'.format(button)).get_rect()
eval('rect_{}'.format(button)).center = LAYERS_POSITIONS['{}'.format(button)]
surface.blit(eval('board_{}'.format(button)), eval('rect_{}'.format(button)))
text = font.render('{}'.format(s_text[inx]), True, FONT_COLOR['menu_button'])
text_rect = text.get_rect()
text_rect.center = eval('rect_{}'.format(button)).center
surface.blit(text, text_rect)
工具类文件主要用来存放游戏需要的小功能函数,实现一些较为繁琐的需求。
score.py完整代码如下:
from mygame.game_2048.settings import *
from mygame.game_2048.utility import *
class Score(object):
def __init__(self):
self.l_scores,self.now,self.number = get_history_scores()
self.best = get_best_score()
self.image_dict = get_image_dict()
self.image_pos, self.coord = gen_score_pos()
def update(self,l_scores):
self.now = sum(l_scores)
return self.now
这里对原程序做了优化,通过初始化数据库参数避免了出现数据库找不到值的情况:ValueError: max() arg is an empty sequence。
player.py部分代码如下:
from mygame.game_2048.settings import *
from mygame.game_2048.utility import *
class Player:
def __init__(self, score_c, l_scores, layer):
self.score = score_c
self.L_SCORES = l_scores
self.layer = layer
self.number = score_c.number
def update(self, l_scores):
self.L_SCORES = l_scores
def move(self, event, l_scores):
self.update(l_scores)
output = keyboard_ctrl(event)
new_scores = self.L_SCORES.copy()
R.hset(RECORD_NAME, '{}'.format(self.number), '{}'.format(new_scores))
self.number += 1
for i in range(GAME_MODE):
for inx in range(pow(GAME_MODE, 2)):
val = self.L_SCORES[inx]
coord_now = self.score.coord[inx]
coord_new = coord_now + output
# 边界移动,不修改
if coord_new not in self.score.coord:
continue
# 同为0值也不修改
elif self.L_SCORES[self.score.coord.index(coord_new)] == val and val == 0:
continue
# 有一个非0值,交换两个值
elif self.L_SCORES[self.score.coord.index(coord_new)] == 0 and val != 0:
new_scores[self.score.coord.index(coord_new)] = val
new_scores[inx] = 0
self.L_SCORES = new_scores
# 两个值不同,不修改
elif self.L_SCORES[self.score.coord.index(coord_new)] != val:
continue
# 两个值相同,将两个值合并
elif self.L_SCORES[self.score.coord.index(coord_new)] == val:
new_scores[self.score.coord.index(coord_new)] = 2 * val
new_scores[inx] = 0
self.L_SCORES = new_scores
代码中的self.number表示一个计数器用来记录当前历史步数。整个2048游戏的运行逻辑和之前的博客内容大致相同,这里不做赘述。这里主要优化了坐标系的使用,简单来说就是:将分数用列表存储,同时将下表映射为坐标,通过键盘输入得一个偏移向量,将当前坐标同偏移向量相加即可得到对应的偏移坐标和偏移坐标对应的值。
start.py完整代码如下:
from mygame.game_2048.settings import * # 导入配置文件
from mygame.game_2048.layers import Layers
from mygame.game_2048.utility import *
from mygame.game_2048.player import Player
from mygame.game_2048.control import Control
class Game(object):
def __init__(self):
py.init()
self.screen = py.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
py.display.set_caption(CAPTION)
self.icon = py.image.load('{}'.format(IMAGES_PATH['icon'])).convert_alpha()
py.display.set_icon(self.icon)
self.screen.fill(BG_COLOR)
self.clock = py.time.Clock()
self.layer = Layers(self.screen)
self.L_SCORES = self.layer.l_scores
self.layer.draw_all(self.L_SCORES) # 绘制游戏界面
self.player = Player(self.layer.score, self.L_SCORES, self.layer) # 交互控制函数
self.gameControl = Control() # 控制器
def run(self):
while True:
key = py.key.get_pressed()
if key[py.K_ESCAPE]: exit()
for event in py.event.get():
if event.type == py.QUIT:
py.quit()
sys.exit()
elif event.type == py.KEYDOWN and self.gameControl.gameOver is False:
self.player.move(event,self.L_SCORES)
self.L_SCORES = self.player.L_SCORES
self.gameControl = self.player.game_update(self.L_SCORES,self.gameControl)
self.L_SCORES = self.player.L_SCORES
self.layer.draw_update(self.L_SCORES, self.gameControl)
elif event.type == py.MOUSEBUTTONDOWN :
self.gameControl = self.player.mouse_click(event,self.gameControl)
self.L_SCORES = self.player.L_SCORES
self.layer.draw_update(self.L_SCORES, self.gameControl)
py.display.update()
self.clock.tick(FPS)
if __name__ == '__main__':
game = Game()
game.run()
通过对整个代码的优化,可以看到启动程序还是比较简洁的。
整个程序在设计上基本实现了我之前的想法,游戏设计的核心算法并不复杂,不过牵涉的知识点相对较多,可以作为一个python的初级练手项目来学习(笑)。