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

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

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

當然,不使用圖片加載游戲也是可以的,如使用py.draw.rect()也能繪制圖像,不過每次加載都繪制圖像會占用游戲大量運算內(nèi)存,并且使用圖片可以自定義自己的游戲風格,修改上也非常便利。設置完成之后,定義一個游戲的初始化模塊:
# 主程序
def game_start():
global screen, rate
py.init()
clock = py.time.Clock()
screen_x = 500 # 請調到合適的大小
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)
# 主界面下設計
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))游戲界面的大小請調節(jié)到合適的尺寸。接下來加載分數(shù)圖片,以便游戲循環(huán)時隨時可以調用。
# 預加載分數(shù)圖
def pre_load_image(background):
imageList = {}
imagePath = './素材/分數(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
# 加載分數(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)
# 用于確定加載的分數(shù)圖像的顯示點位
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ù)字在分數(shù)圖片上。最后一個函數(shù)用于確定每個坐標在游戲界面的顯示位置,并將其一一綁定。加載完成圖像之后,就需要完成關鍵的移動邏輯,先上代碼:
# 移動邏輯
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首先導入預先確定好的坐標,移動變量。根據(jù)前面分析的游戲邏輯,每次輸入移動向量后游戲內(nèi)的所有方塊都需要移動,相同分數(shù)的方塊需要一次性合并到一起,并且不能留空。詳細分析一下就是:
- 輸入一個移動向量(x,y),如(+1,0)表示方塊向右移動一格。
- 對所有的原坐標進行計算并保留為移動后坐標,提取前后兩次坐標對應的分數(shù)
- 從1號標記開始循環(huán)判斷:
- 0值移動:如果移動后的分數(shù)為0,用舊坐標分數(shù)替代新坐標的分數(shù),并刪除舊坐標的分數(shù)
- 無法移動:移動后的坐標與移動前的坐標相同,那么不做改變
- 合并移動:新舊坐標對應的分數(shù)相同,那么新坐標分數(shù)x2,舊坐標分數(shù)刪除
- 邊界移動:方塊已經(jīng)處于移動的邊界,無法移動,不做修改
- 非邊界移動:新舊坐標對應的分數(shù)不同,且新坐標的下一個坐標對應的分數(shù)也不同,不做修改;新舊坐標對應的分數(shù)不同,且新坐標的下一個坐標對應的分數(shù)相同,修改
- 循環(huán)整個邏輯,直到所有坐標對應的分數(shù)不再發(fā)生改變
通過上述分析,移動邏輯函數(shù)實現(xiàn)了輸入一個方向游戲內(nèi)的分數(shù)動態(tài)發(fā)生變化。最后我們還需要一個游戲結束的函數(shù):
# 游戲結束
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)每次合并之后能在空白方塊處隨機生成2或4中的一個新分數(shù),生成概率按照當前游戲中的2和4的數(shù)量為基礎。
# 隨機得分生成
def random_score(score_list):
values = list(score_list.values())
pro = [2] * (2 + values.count(2)) + [4] * (1 + values.count(4)) # 以當前分數(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' # 游戲結束
else:
score_list[random.choice(blank)] = random.choice(pro)
return score_list一個得分統(tǒng)計器,每次游戲運行是統(tǒng)計當前得分和歷史最高得分:
# 統(tǒ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)最后補充完整的游戲啟動器:
# 主程序
def game_start():
global screen, rate
py.init()
clock = py.time.Clock()
screen_x = 500 # 請調到合適的大小
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)
# 主界面下設計
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ù)據(jù)
draw_best(BackGround)
posList = image_pos_list(bgSecondRect)
imageList = pre_load_image(bgSecondRect)
scoreList = dict(zip(list(range(1, 17)), [0] * 15 + [2])) # 分數(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()步驟五
啟動游戲
運行之前別忘了啟動redis服務器。運行效果圖:(游戲界面設計的不夠好。。。。,本來打算再加入一些小道具比如說:撤銷,全屏合并等功能)

寫在最后:有時間的話考慮再做一個菜單界面。最后給個懶人包:2048提取碼:utfu
到此這篇關于Python制作簡易版2048小游戲的文章就介紹到這了,更多相關Python 2048內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決使用python print打印函數(shù)返回值多一個None的問題
這篇文章主要介紹了解決使用python print打印函數(shù)返回值多一個None的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04
python3使用mutagen進行音頻元數(shù)據(jù)處理的方法
mutagen是一個處理音頻元數(shù)據(jù)的python模塊,支持多種音頻格式,是一個純粹的python庫,僅依賴python標準庫,可在Python?3.7及以上版本運行,支持Linux、Windows?和?macOS系統(tǒng),這篇文章主要介紹了python3使用mutagen進行音頻元數(shù)據(jù)處理,需要的朋友可以參考下2022-10-10
Python中如何使用多線程優(yōu)化For循環(huán)
這篇文章主要為大家詳細介紹了在Python中如何使用多線程實現(xiàn)優(yōu)化For循環(huán),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2025-01-01
Win10?Anaconda?新建環(huán)境安裝python-pcl的步驟
這篇文章主要介紹了Win10?Anaconda?新建環(huán)境安裝python-pcl的方法,至于VS環(huán)境下安裝C++?版本的pcl也可以按照此文提供的步驟安裝實現(xiàn),需要的朋友可以參考下2022-04-04

