Python制作簡易版2048小游戲
今天我們來動手實現(xiàn)一款2048小游戲。這款游戲的精髓就玩家能夠在于通過滑動屏幕合并相同數(shù)字,直到不能再合并為止。玩法可以說是非常的簡單,但挑戰(zhàn)性也是十足的。話不多說,讓我們從0開始實現(xiàn)!
目標(biāo)效果
大致要實現(xiàn)的效果如下:

設(shè)計開始
首先簡單分析一下游戲的邏輯:
- 輸入移動方向,游戲內(nèi)所有方塊都朝指定方向移動
- 同方向移動的方塊,數(shù)字相同則合并,然后生成一個合并的方塊
- 合并后生成新的方塊,無法生成新方塊時游戲結(jié)束
- 用一系列的顏色來區(qū)分不同分?jǐn)?shù)的方塊(可有可無,純粹是為了美觀)
ok,游戲內(nèi)再邏輯已經(jīng)很清晰了。現(xiàn)在開始實現(xiàn):
步驟一
新建一個文件夾用來放需要的游戲素材
步驟二
新建一個python程序,可以命名為2048,放在素材目錄的同級文件夾下
步驟三
導(dǎo)入需要的依賴庫:
import pygame as py import sys, random, time, redis, os,math import numpy as np
依賴庫中的redis是一個額外的數(shù)據(jù)庫,用來存取游戲歷史數(shù)據(jù),需要的可以考慮安裝,不需要的用excel表代替也可以。
首先需要思考的是,游戲內(nèi)的方塊的移動本質(zhì)上是坐標(biāo)的變換,并且方塊的坐標(biāo)是固定的,也就是說,每次輸入一個方向就按照一個移動函數(shù)將所有方塊的坐標(biāo)進(jìn)行對應(yīng)的轉(zhuǎn)換。那么,如此以來,就需要建立一個坐標(biāo)系用以標(biāo)記方塊的坐標(biāo)。
因為是4x4的游戲,那么就按照(1,1),(1,2),(1,3),...,(4,4)建立游戲坐標(biāo),然而相比直接移動坐標(biāo)還是比較麻煩,一個簡單的想法是,每個方塊給一個唯一的標(biāo)記,如我們需要實現(xiàn)4x4的游戲,就需要16個記號。而每一個標(biāo)記就對應(yīng)了唯一且固定的坐標(biāo)。給出如下代碼:
# 預(yù)加載移動邏輯
def pre_move():
numberPos = {}
for num in range(1, 17):
row1, row2 = divmod(num, 4)
row = row1 + np.sign(row2)
column = [row2 if row2 != 0 else 4][0]
numberPos['{}'.format([row, column])] = num
return numberPos這里的numberPos實際上就是{‘{1,1}’:1,’{1,2}‘:2......}。當(dāng)然如果想設(shè)計5x5或者6x6的只需要把循環(huán)里面的17和4改成25和5或36和6就行。
ok,有了坐標(biāo)接下來的問題好解決了。
步驟四
在新建的素材文件夾內(nèi)放入一些圖片方塊(正方形)用來表示每個不同分?jǐn)?shù)的方塊。如下圖所示:

這里的顏色大家可以隨意選擇,只要不與游戲背景色太接近即可。在圖片數(shù)量夠多的情況下甚至能夠?qū)崿F(xiàn)顏色動態(tài)變換的方塊,當(dāng)然這都是后話,設(shè)定好每個分?jǐn)?shù)的圖片后,再設(shè)置一個背景用的圖片,一個游戲圖標(biāo)用圖片,一個字體,字體單獨(dú)用來顯示文字。

當(dāng)然,不使用圖片加載游戲也是可以的,如使用py.draw.rect()也能繪制圖像,不過每次加載都繪制圖像會占用游戲大量運(yùn)算內(nèi)存,并且使用圖片可以自定義自己的游戲風(fēng)格,修改上也非常便利。設(shè)置完成之后,定義一個游戲的初始化模塊:
# 主程序
def game_start():
global screen, rate
py.init()
clock = py.time.Clock()
screen_x = 500 # 請調(diào)到合適的大小
screen_y = math.ceil(screen_x * rate / rate2)
screen = py.display.set_mode((screen_x, screen_y), depth=32)
py.display.set_caption("終極2048")
BackGround = [251, 248, 239] # 灰色
Icon = py.image.load('./素材/icon.png').convert_alpha()
py.display.set_icon(Icon)
screen.fill(color=BackGround)
# 主界面下設(shè)計
width = math.floor(screen_x * rate)
bgSecond = py.image.load('./素材/BG_02.png').convert_alpha()
bgSecond = py.transform.smoothscale(bgSecond, (width, width))
bgSecondRect = bgSecond.get_rect()
bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))游戲界面的大小請調(diào)節(jié)到合適的尺寸。接下來加載分?jǐn)?shù)圖片,以便游戲循環(huán)時隨時可以調(diào)用。
# 預(yù)加載分?jǐn)?shù)圖
def pre_load_image(background):
imageList = {}
imagePath = './素材/分?jǐn)?shù)/'
image_filenames = [i for i in os.listdir(imagePath)]
width = math.floor(background.width * (1 - internalWidth) / 4)
for name in image_filenames:
image = py.transform.smoothscale(py.image.load(imagePath + name).convert_alpha(), (width, width))
imageList[name.replace('.png', '')] = image
return imageList
# 加載分?jǐn)?shù)圖像
def draw_image(score_list, image_list, pos_list):
for pos_num in score_list:
score = score_list[pos_num]
scoreSurf = BasicFont01.render('{}'.format(score), True, (0, 0, 0))
scoreRect = scoreSurf.get_rect()
if score <= 4096:
image = image_list['{}'.format(score)]
else:
image = image_list['4096']
imageRect = image.get_rect()
imageRect.topleft = pos_list['{}'.format(pos_num)]
scoreRect.center = imageRect.center
screen.blit(image, imageRect)
if score > 0:
screen.blit(scoreSurf, scoreRect)
# 圖像位置列表,表示為(x,y)
# 用于確定加載的分?jǐn)?shù)圖像的顯示點(diǎn)位
def image_pos_list(background):
pre_x = background.topleft[0]
pre_y = background.topleft[-1]
internalLong = math.ceil(internalWidth / 5 * background.width)
imageLong = math.floor((1 - internalWidth) / 4 * background.width)
posList = dict(zip(list(range(1, 17)), [''] * 16))
for num in range(1, 17):
row1, row2 = divmod(num, 4)
row = row1 + np.sign(row2)
column = [row2 if row2 != 0 else 4][0]
image_x = pre_x + internalLong * column + imageLong * (column - 1)
image_y = pre_y + internalLong * row + imageLong * (row - 1)
posList['{}'.format(num)] = (image_x, image_y)
return posList這里用了三個函數(shù)來加載游戲圖片,分表表示:提取圖片名保存到列表中,繪制游戲中的2,4,8等等數(shù)字在分?jǐn)?shù)圖片上。最后一個函數(shù)用于確定每個坐標(biāo)在游戲界面的顯示位置,并將其一一綁定。加載完成圖像之后,就需要完成關(guān)鍵的移動邏輯,先上代碼:
# 移動邏輯
def number_move(number_pos, move_input, score_list):
values = list(number_pos.values())
keys = list(number_pos.keys())
numberPosReverse = dict(zip(values, keys))
newScoreList = score_list.copy()
oldScoreList = {}
while newScoreList != oldScoreList:
oldScoreList = newScoreList.copy()
for num in range(1, 17):
pos = eval(numberPosReverse[num])
x, y = pos[0] + move_input[0], pos[1] + move_input[1]
pos[0] = [x if 1 <= x <= 4 else pos[0]][0]
pos[1] = [y if 1 <= y <= 4 else pos[1]][0]
number = number_pos['{}'.format(pos)]
oldNumberScore = newScoreList[num]
nextNumberScore = newScoreList[number]
syn = list(map(lambda x, y: abs(x) * abs(y), move_input, pos))
# 0值移動
if nextNumberScore == 0:
newScoreList[number] = oldNumberScore
newScoreList[num] = 0
# 無法移動
elif num == number:
pass
# 合并移動
elif oldNumberScore == nextNumberScore and num != number:
newScoreList[number] = 2 * oldNumberScore
newScoreList[num] = 0
# 邊界移動
elif oldNumberScore != nextNumberScore and 1 in syn or 4 not in syn:
pass
# 非邊界移動
elif oldNumberScore != nextNumberScore and 1 not in syn and 4 not in syn:
x, y = pos[0] + move_input[0], pos[1] + move_input[1]
next2NumberScore = newScoreList[number_pos['{}'.format([x, y])]]
if next2NumberScore != nextNumberScore:
pass
elif next2NumberScore == nextNumberScore:
newScoreList[number_pos['{}'.format([x, y])]] = 2 * next2NumberScore
newScoreList[number] = oldNumberScore
newScoreList[num] = 0
return newScoreList首先導(dǎo)入預(yù)先確定好的坐標(biāo),移動變量。根據(jù)前面分析的游戲邏輯,每次輸入移動向量后游戲內(nèi)的所有方塊都需要移動,相同分?jǐn)?shù)的方塊需要一次性合并到一起,并且不能留空。詳細(xì)分析一下就是:
- 輸入一個移動向量(x,y),如(+1,0)表示方塊向右移動一格。
- 對所有的原坐標(biāo)進(jìn)行計算并保留為移動后坐標(biāo),提取前后兩次坐標(biāo)對應(yīng)的分?jǐn)?shù)
- 從1號標(biāo)記開始循環(huán)判斷:
- 0值移動:如果移動后的分?jǐn)?shù)為0,用舊坐標(biāo)分?jǐn)?shù)替代新坐標(biāo)的分?jǐn)?shù),并刪除舊坐標(biāo)的分?jǐn)?shù)
- 無法移動:移動后的坐標(biāo)與移動前的坐標(biāo)相同,那么不做改變
- 合并移動:新舊坐標(biāo)對應(yīng)的分?jǐn)?shù)相同,那么新坐標(biāo)分?jǐn)?shù)x2,舊坐標(biāo)分?jǐn)?shù)刪除
- 邊界移動:方塊已經(jīng)處于移動的邊界,無法移動,不做修改
- 非邊界移動:新舊坐標(biāo)對應(yīng)的分?jǐn)?shù)不同,且新坐標(biāo)的下一個坐標(biāo)對應(yīng)的分?jǐn)?shù)也不同,不做修改;新舊坐標(biāo)對應(yīng)的分?jǐn)?shù)不同,且新坐標(biāo)的下一個坐標(biāo)對應(yīng)的分?jǐn)?shù)相同,修改
- 循環(huán)整個邏輯,直到所有坐標(biāo)對應(yīng)的分?jǐn)?shù)不再發(fā)生改變
通過上述分析,移動邏輯函數(shù)實現(xiàn)了輸入一個方向游戲內(nèi)的分?jǐn)?shù)動態(tài)發(fā)生變化。最后我們還需要一個游戲結(jié)束的函數(shù):
# 游戲結(jié)束
def game_over(score,bg):
ip = '127.0.0.1'
password = None
r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True)
r.hset('2048','{}'.format(time.localtime()),score)
py.draw.rect(screen,bg,[0,0,screen.get_width(),screen.get_height()],0)
BasicFont02 = py.font.SysFont('/素材/simkai.ttf', 40)
overSurf = BasicFont01.render('Game Over', True, (0, 0, 0))
overRect = overSurf.get_rect()
overRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() / 2))
scoreSurf = BasicFont02.render('最終得分:', True, (0, 0, 0))
scoreRect = scoreSurf.get_rect()
scoreRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.6))
numberSurf = BasicFont02.render('{}'.format(score), True, (0, 0, 0))
numberRect = numberSurf.get_rect()
numberRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.7))
time.sleep(3)
sys.exit()一個鍵盤控制代碼,實現(xiàn)鍵盤控制游戲:
# 鍵盤控制函數(shù)
def keyboard_ctrl(event):
move_output = [0, 0]
if event.key == py.K_UP:
move_output = [-1, 0]
elif event.key == py.K_DOWN:
move_output = [1, 0]
elif event.key == py.K_RIGHT:
move_output = [0, 1]
elif event.key == py.K_LEFT:
move_output = [0, -1]
return move_output一個新方塊生成器,實現(xiàn)每次合并之后能在空白方塊處隨機(jī)生成2或4中的一個新分?jǐn)?shù),生成概率按照當(dāng)前游戲中的2和4的數(shù)量為基礎(chǔ)。
# 隨機(jī)得分生成
def random_score(score_list):
values = list(score_list.values())
pro = [2] * (2 + values.count(2)) + [4] * (1 + values.count(4)) # 以當(dāng)前分?jǐn)?shù)圖中2或4出現(xiàn)的頻率為概率
blank = [[i if score_list[i] == 0 else 0][0] for i in range(1, 17)]
blank = list(set(blank))
blank.remove(0)
if not blank:
return 'GameOver' # 游戲結(jié)束
else:
score_list[random.choice(blank)] = random.choice(pro)
return score_list一個得分統(tǒng)計器,每次游戲運(yùn)行是統(tǒng)計當(dāng)前得分和歷史最高得分:
# 統(tǒng)計并記錄當(dāng)前得分
def record_score(score_list, background):
totalScore = 0
values = list(score_list.values())
for i in values: totalScore += i
scoreSurf = BasicFont01.render('得分:{}'.format(totalScore), True, (0, 0, 0))
scoreRect = scoreSurf.get_rect()
scoreRect.topleft = (math.floor(0.1 * screen.get_width()), math.floor(0.05 * screen.get_height()))
scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width())
scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height())
py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0)
screen.blit(scoreSurf, scoreRect)
return totalScore
# 繪制歷史最高得分
def draw_best(background):
ip = '127.0.0.1'
password = None
r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True)
scores=[eval(i) for i in list(r.hgetall('2048').values())]
best_scores=max(scores)
scoreSurf=BasicFont01.render('最高得分:{}'.format(best_scores),True,(0,0,0))
scoreRect=scoreSurf.get_rect()
scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width())
scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height())
scoreRect.topright = (math.floor(0.9 * screen.get_width()), math.floor(0.05 * screen.get_height()))
py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0)
screen.blit(scoreSurf, scoreRect)最后補(bǔ)充完整的游戲啟動器:
# 主程序
def game_start():
global screen, rate
py.init()
clock = py.time.Clock()
screen_x = 500 # 請調(diào)到合適的大小
screen_y = math.ceil(screen_x * rate / rate2)
screen = py.display.set_mode((screen_x, screen_y), depth=32)
py.display.set_caption("終極2048")
BackGround = [251, 248, 239] # 灰色
Icon = py.image.load('./素材/icon.png').convert_alpha()
py.display.set_icon(Icon)
screen.fill(color=BackGround)
# 主界面下設(shè)計
width = math.floor(screen_x * rate)
bgSecond = py.image.load('./素材/BG_02.png').convert_alpha()
bgSecond = py.transform.smoothscale(bgSecond, (width, width))
bgSecondRect = bgSecond.get_rect()
bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))
# 主界面上部分設(shè)計
# 預(yù)加載數(shù)據(jù)
draw_best(BackGround)
posList = image_pos_list(bgSecondRect)
imageList = pre_load_image(bgSecondRect)
scoreList = dict(zip(list(range(1, 17)), [0] * 15 + [2])) # 分?jǐn)?shù)表
numberPos = pre_move()
scoreList = random_score(scoreList)
totalScore=0
# 主循環(huán)
while True:
screen.blit(bgSecond, bgSecondRect) # 刷新屏幕
if scoreList == 'GameOver':
game_over(totalScore,BackGround)
draw_image(scoreList, imageList, posList) # 繪制得分
totalScore = record_score(scoreList, BackGround)
key = py.key.get_pressed()
if key[py.K_ESCAPE]: exit()
for event in py.event.get():
if event.type == py.QUIT:
sys.exit()
elif event.type == py.KEYDOWN:
move_input = keyboard_ctrl(event) # 按下按鍵
scoreList = number_move(numberPos, move_input, scoreList) # 移動數(shù)字
scoreList = random_score(scoreList) # 在按下按鍵后生成新的數(shù)字
py.display.update()
clock.tick(FPS)
if __name__ == '__main__':
py.font.init()
BasicFont01 = py.font.Font('./素材/simkai.ttf', 30)
screen = py.display.set_mode((500, 500))
rate = 0.95 # 游戲主界面下的寬度占整個游戲界面寬度的比例
rate2 = 0.7 # 游戲主界面下的高度占整個游戲界面高度的比例
internalWidth = 0.1 # 間隙比例
FPS = 50 # 游戲幀率
game_start()步驟五
啟動游戲
運(yùn)行之前別忘了啟動redis服務(wù)器。運(yùn)行效果圖:(游戲界面設(shè)計的不夠好。。。。,本來打算再加入一些小道具比如說:撤銷,全屏合并等功能)

寫在最后:有時間的話考慮再做一個菜單界面。最后給個懶人包:2048提取碼:utfu
到此這篇關(guān)于Python制作簡易版2048小游戲的文章就介紹到這了,更多相關(guān)Python 2048內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
pyqt實現(xiàn).ui文件批量轉(zhuǎn)換為對應(yīng).py文件腳本
今天小編就為大家分享一篇pyqt實現(xiàn).ui文件批量轉(zhuǎn)換為對應(yīng).py文件腳本,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-06-06
Python socket實現(xiàn)的簡單通信功能示例
這篇文章主要介紹了Python socket實現(xiàn)的簡單通信功能,結(jié)合實例形式分析了Python socket通信的相關(guān)概念、原理、客戶端與服務(wù)器端實現(xiàn)技巧以及socketserver模塊多并發(fā)簡單實現(xiàn)方法,需要的朋友可以參考下2018-08-08
python批量修改文件夾及其子文件夾下的文件內(nèi)容
這篇文章主要為大家詳細(xì)介紹了python批量修改文件夾及其子文件夾下的文件內(nèi)容,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-03-03
淺談python的dataframe與series的創(chuàng)建方法
今天小編就為大家分享一篇淺談python的dataframe與series的創(chuàng)建方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11
Python命令行參數(shù)解析之a(chǎn)rgparse模塊詳解
這篇文章主要介紹了Python命令行參數(shù)解析之a(chǎn)rgparse模塊詳解,argparse?是?Python?的一個標(biāo)準(zhǔn)庫,用于命令行參數(shù)的解析,這意味著我們無需在代碼中手動為變量賦值,而是可以直接在命令行中向程序傳遞相應(yīng)的參數(shù),再由變量去讀取這些參數(shù),需要的朋友可以參考下2023-08-08
Windows下Eclipse+PyDev配置Python+PyQt4開發(fā)環(huán)境
這篇文章主要介紹了Windows下Eclipse+PyDev配置Python+PyQt4開發(fā)環(huán)境的相關(guān)資料,需要的朋友可以參考下2016-05-05

