<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
今天我們來動手實現一款2048小遊戲。這款遊戲的精髓就玩家能夠在於通過滑動螢幕合併相同數位,直到不能再合併為止。玩法可以說是非常的簡單,但挑戰性也是十足的。話不多說,讓我們從0開始實現!
大致要實現的效果如下:
首先簡單分析一下游戲的邏輯:
ok,遊戲內再邏輯已經很清晰了。現在開始實現:
新建一個資料夾用來放需要的遊戲素材
新建一個python程式,可以命名為2048,放在素材目錄的同級資料夾下
匯入需要的依賴庫:
import pygame as py import sys, random, time, redis, os,math import numpy as np
依賴庫中的redis是一個額外的資料庫,用來存取遊戲歷史資料,需要的可以考慮安裝,不需要的用excel表代替也可以。
首先需要思考的是,遊戲內的方塊的移動本質上是座標的變換,並且方塊的座標是固定的,也就是說,每次輸入一個方向就按照一個移動函數將所有方塊的座標進行對應的轉換。那麼,如此以來,就需要建立一個座標系用以標記方塊的座標。
因為是4x4的遊戲,那麼就按照(1,1),(1,2),(1,3),...,(4,4)建立遊戲座標,然而相比直接移動座標還是比較麻煩,一個簡單的想法是,每個方塊給一個唯一的標記,如我們需要實現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的只需要把迴圈裡面的17和4改成25和5或36和6就行。
ok,有了座標接下來的問題好解決了。
在新建的素材資料夾內放入一些圖片方塊(正方形)用來表示每個不同分數的方塊。如下圖所示:
這裡的顏色大家可以隨意選擇,只要不與遊戲背景色太接近即可。在圖片數量夠多的情況下甚至能夠實現顏色動態變換的方塊,當然這都是後話,設定好每個分數的圖片後,再設定一個背景用的圖片,一個遊戲圖示用圖片,一個字型,字型單獨用來顯示文字。
當然,不使用圖片載入遊戲也是可以的,如使用py.draw.rect()也能繪製影象,不過每次載入都繪製影象會佔用遊戲大量運算記憶體,並且使用圖片可以自定義自己的遊戲風格,修改上也非常便利。設定完成之後,定義一個遊戲的初始化模組:
# 主程式 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))
遊戲介面的大小請調節到合適的尺寸。接下來載入分數圖片,以便遊戲迴圈時隨時可以呼叫。
# 預載入分數圖 def pre_load_image(background): imageList = {} imagePath = './素材/分數/' 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 # 載入分數影象 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) # 用於確定載入的分數影象的顯示點位 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
這裡用了三個函數來載入遊戲圖片,分表表示:提取圖片名儲存到列表中,繪製遊戲中的2,4,8等等數位在分數圖片上。最後一個函數用於確定每個座標在遊戲介面的顯示位置,並將其一一系結。載入完成影象之後,就需要完成關鍵的移動邏輯,先上程式碼:
# 移動邏輯 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
首先匯入預先確定好的座標,移動變數。根據前面分析的遊戲邏輯,每次輸入移動向量後遊戲內的所有方塊都需要移動,相同分數的方塊需要一次性合併到一起,並且不能留空。詳細分析一下就是:
通過上述分析,移動邏輯函數實現了輸入一個方向遊戲內的分數動態發生變化。最後我們還需要一個遊戲結束的函數:
# 遊戲結束 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()
一個鍵盤控制程式碼,實現鍵盤控制遊戲:
# 鍵盤控制函數 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
一個新方塊生成器,實現每次合併之後能在空白方塊處隨機生成2或4中的一個新分數,生成概率按照當前遊戲中的2和4的數量為基礎。
# 隨機得分生成 def random_score(score_list): values = list(score_list.values()) pro = [2] * (2 + values.count(2)) + [4] * (1 + values.count(4)) # 以當前分數圖中2或4出現的頻率為概率 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
一個得分統計器,每次遊戲執行是統計當前得分和歷史最高得分:
# 統計並記錄當前得分 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)) # 主介面上部分設計 # 預載入資料 draw_best(BackGround) posList = image_pos_list(bgSecondRect) imageList = pre_load_image(bgSecondRect) scoreList = dict(zip(list(range(1, 17)), [0] * 15 + [2])) # 分數表 numberPos = pre_move() scoreList = random_score(scoreList) totalScore=0 # 主迴圈 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) # 移動數位 scoreList = random_score(scoreList) # 在按下按鍵後生成新的數位 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內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45