** SlidePuzzle滑動拼圖 **
先上個圖(●'?'●)
ps:本人比較笨,3 * 3 的都要解半天,各位可以自行增加難度
游戲中比較核心的三個方法:
方法一,就是生成有規律的二維數組,例如:
在2 x 2下: [ [ 1, 3 ] , [ 2, None ] ]
在3 x 3下: [ [ 1, 4, 7 ] , [ 2, 5, 8 ] , [ 3, 6, None ] ]
在4 x 4下: [ [ 1, 5, 9, 13 ] , [ 2, 6, 10, 14 ] , [ 3, 7, 11, 15 ] , [ 4, 8, 12, None ] ]
# 生成剛開始的board序列
def getStartingBoard():
# 返回一個board的序列
# 舉個例子,如果board的列數和行數都是3,則返回以下數據
# [[1, 4, 7], [2, 5, 8], [3, 6, None]]
counter = 1
board = []
for x in range(BOARDWIDTH):
column = []
for y in range(BOARDHEIGHT):
column.append(counter)
counter += BOARDWIDTH
board.append(column)
counter -= BOARDWIDTH * (BOARDHEIGHT - 1) + BOARDWIDTH - 1
board[BOARDWIDTH-1][BOARDHEIGHT-1] = None
return board
方法二,打亂上面生成的二維數組,并返回打亂的結果和儲存打亂過程的每一步的一個序列
# 生成新的拼圖(就是隨機打亂已經排序好的board,numSlides為打亂的步數)
def generateNewPuzzle(numSlides):
sequence = [] # 儲存打亂的步數的序列
board = getStartingBoard() # 獲得開始board的序列
drawBoard(board, '') # 繪制board
pygame.display.update() # 更新屏幕
pygame.time.wait(500) # 暫停500毫秒 for effect
lastMove = None
for i in range(numSlides):
move = getRandomMove(board, lastMove)# 隨機的移動一步
slideAnimation(board, move, 'Generating new puzzle...', int(TILESIZE / 3)) # 滑動的動畫
makeMove(board, move) # 交換空白方塊的位置
sequence.append(move) # 將這個步驟添加到sequence序列中
lastMove = move
return (board, sequence)
方法三,根據所有操作的的序列,恢復游戲
# 根據所有操作的的序列,恢復游戲
def resetAnimation(board, allMoves):
revAllMoves = allMoves[:] # 復制一份所有操作的的序列
revAllMoves.reverse() # 倒序排列
for move in revAllMoves: # 取出每一步,做相反的操作(就能恢復為原來的最初的序列)
if move == UP:
oppositeMove = DOWN
elif move == DOWN:
oppositeMove = UP
elif move == RIGHT:
oppositeMove = LEFT
elif move == LEFT:
oppositeMove = RIGHT
slideAnimation(board, oppositeMove, '', int(TILESIZE / 2)) # 滑動的動畫
makeMove(board, oppositeMove) # 交換空白方塊的位置
理一下思路:
1,繪制一個640*480的窗口
2,根據行數和列數生成一個有規律的二維數組 SOLVEDBOARD
3,打亂上面生成的二維數組,返回打亂的結果 mainBoard 和一個儲存打亂過程的每一步的序列solutionSeq
4,定義一個記錄玩家操作的的序列 allMoves
5,繪制主Board,邊框,左上角的文字提示,以及右下角的三個按鈕
6,游戲主循環,判斷mainBoard是否和SOLVEDBOARD相等,相等就說明拼圖已經還原
7,鼠標、鍵盤事件處理
8,交換空白方塊與其上下左右方塊的位置,記錄每次操作到 allMoves 中
9,點擊恢復按鈕:調用resetAnimation方法,allMoves 作為參數,并清空 allMoves
10,點中的是解決方案按鈕:調用resetAnimation方法,solutionSeq + allMoves 作為參數,并清空 allMoves
11,點中的是新游戲按鈕:調用generateNewPuzzle方法生成新的 mainBoard 和 solutionSeq ,并清空 allMoves
在第8步中,用到一個** DISPLAYSURF.copy() **方法
copy()方法會返回一個新的Surface,上面繪制著和原來一樣的內容,在調用完copy方法后,如果我們在這個Surface上面繪制image內容,他將不會改變這個原來那個Surface
完整代碼:
# -*- coding: UTF-8 -*-
'''
Created on 2016年11月28日
@author: 小峰峰
'''
import random, pygame, sys
from pygame.locals import *
BOARDWIDTH = 4 # board的列數
BOARDHEIGHT = 4 # board的行數
TILESIZE = 80 # 方塊的大小
WINDOWWIDTH = 640 # 窗口寬度
WINDOWHEIGHT = 480 # 窗口高度
FPS = 30 # 幀率
BLANK = None
# 定義幾個顏色 (R G B)
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
BRIGHTBLUE = ( 0, 50, 255)
DARKTURQUOISE = ( 3, 54, 73)
GREEN = ( 0, 204, 0)
BGCOLOR = DARKTURQUOISE # 背景色
TILECOLOR = GREEN # 方塊顏色
TEXTCOLOR = WHITE # 文字顏色
BORDERCOLOR = BRIGHTBLUE # 邊框顏色
BASICFONTSIZE = 20 # 字體大小
BUTTONCOLOR = WHITE # 按鈕顏色
BUTTONTEXTCOLOR = BLACK # 按鈕文字顏色
MESSAGECOLOR = WHITE # 消息顏色
XMARGIN = int((WINDOWWIDTH - (TILESIZE * BOARDWIDTH + (BOARDWIDTH - 1))) / 2) # X軸邊距
YMARGIN = int((WINDOWHEIGHT - (TILESIZE * BOARDHEIGHT + (BOARDHEIGHT - 1))) / 2) # Y軸邊距
# 定義"上下左右"操作
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
def main():
# 全局變量
global FPSCLOCK, DISPLAYSURF, BASICFONT, RESET_SURF, RESET_RECT, NEW_SURF, NEW_RECT, SOLVE_SURF, SOLVE_RECT
pygame.init() # 初始化pygame
FPSCLOCK = pygame.time.Clock() # 獲得pygame時鐘
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) # 設置窗口大小
pygame.display.set_caption('Slide Puzzle') # 設置標題
BASICFONT = pygame.font.Font('PAPYRUS.ttf', BASICFONTSIZE) # 設置字體,和字體大小
# 操作按鈕的屬性
RESET_SURF, RESET_RECT = makeText(' Reset ', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 105) # 恢復
NEW_SURF, NEW_RECT = makeText('New Game', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 70) # 新游戲
SOLVE_SURF, SOLVE_RECT = makeText(' Solve ', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 35) # 解決方案
mainBoard, solutionSeq = generateNewPuzzle(80)# 打亂生成的拼圖(返回Board序列,和對應的解決序列)
SOLVEDBOARD = getStartingBoard() # 獲得生成剛開始的board序列,作為是否解決的參照
allMoves = [] # 記錄玩家操作的的序列
while True: # 游戲主循環
slideTo = None # 滑動的方向
msg = '' # 左上角顯示的消息內容
if mainBoard == SOLVEDBOARD: # 如果Board序列等于SOLVEDBOARD序列
msg = 'Solved!' # 顯示消息已經修復好拼圖了
drawBoard(mainBoard, msg)
for event in pygame.event.get(): # 事件處理
if event.type == MOUSEBUTTONUP: # 如果是鼠標點擊事件
spotx, spoty = getSpotClicked(mainBoard, event.pos[0], event.pos[1]) # 根據鼠標點擊的像素坐標,獲得方塊的坐標
if (spotx, spoty) == (None, None): # 如果沒有點方塊
# 檢查是否點中右下角的幾個按鈕
if RESET_RECT.collidepoint(event.pos): # 點中的是恢復按鈕
resetAnimation(mainBoard, allMoves) # 將玩家操作的的序列作為參數,恢復游戲
allMoves = [] # 清空 allMoves
elif NEW_RECT.collidepoint(event.pos): # 點中的是新游戲
mainBoard, solutionSeq = generateNewPuzzle(80) # 重新生成游戲
allMoves = [] # 清空 allMoves
elif SOLVE_RECT.collidepoint(event.pos): # 點中的是解決方案
resetAnimation(mainBoard, solutionSeq + allMoves) # 將對應的解決序列和玩家操作的的序列作為參數,恢復游戲
allMoves = [] # 清空 allMoves
else:
# 檢查點中的方塊是否與空白的方塊相鄰
blankx, blanky = getBlankPosition(mainBoard) # 獲得空白方塊的坐標
if spotx == blankx + 1 and spoty == blanky: # 如果點中的方塊在空白方塊的右邊,就往左滑
slideTo = LEFT
elif spotx == blankx - 1 and spoty == blanky:# 如果點中的方塊在空白方塊的左邊,就往右滑
slideTo = RIGHT
elif spotx == blankx and spoty == blanky + 1:# 如果點中的方塊在空白方塊的下邊,就往上滑
slideTo = UP
elif spotx == blankx and spoty == blanky - 1:# 如果點中的方塊在空白方塊的上邊,就往下滑
slideTo = DOWN
elif event.type == KEYUP: # 如果是按鍵操作
# 檢查是否可以移動,如果可以就滑動方塊
if event.key in (K_LEFT, K_a) and isValidMove(mainBoard, LEFT):
slideTo = LEFT
elif event.key in (K_RIGHT, K_d) and isValidMove(mainBoard, RIGHT):
slideTo = RIGHT
elif event.key in (K_UP, K_w) and isValidMove(mainBoard, UP):
slideTo = UP
elif event.key in (K_DOWN, K_s) and isValidMove(mainBoard, DOWN):
slideTo = DOWN
elif event.key == K_ESCAPE: # 如果是ESC鍵,退出
pygame.quit()
sys.exit()
elif event.type == QUIT: # 如果是退出操作,退出
pygame.quit()
sys.exit()
if slideTo:
# 根據slideTo的值來操作滑動方塊
slideAnimation(mainBoard, slideTo, 'Click tile or press arrow keys to slide.', 8) # 滑動的操作
makeMove(mainBoard, slideTo) # 交換空白方塊與其上下左右方塊的位置
allMoves.append(slideTo) # 記錄下這次滑動的操作
pygame.display.update() # 更新屏幕
FPSCLOCK.tick(FPS) # 設置幀率
# 生成剛開始的board序列
def getStartingBoard():
# 返回一個board的序列
# 舉個例子,如果board的列數和行數都是3,則返回以下數據
# [[1, 4, 7], [2, 5, 8], [3, 6, None]]
counter = 1
board = []
for x in range(BOARDWIDTH):
column = []
for y in range(BOARDHEIGHT):
column.append(counter)
counter += BOARDWIDTH
board.append(column)
counter -= BOARDWIDTH * (BOARDHEIGHT - 1) + BOARDWIDTH - 1
board[BOARDWIDTH-1][BOARDHEIGHT-1] = None
print board
return board
# 獲得空白方塊的位置
def getBlankPosition(board):
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if board[x][y] == None: # 窮舉board中所有的方塊,判斷為None的就是空白方塊
return (x, y)
# 交換空白方塊與其上下左右方塊的位置
def makeMove(board, move):
blankx, blanky = getBlankPosition(board) # 獲得空白方塊的位置
# 根據slideTo交換位置
if move == UP:
board[blankx][blanky], board[blankx][blanky + 1] = board[blankx][blanky + 1], board[blankx][blanky]
elif move == DOWN:
board[blankx][blanky], board[blankx][blanky - 1] = board[blankx][blanky - 1], board[blankx][blanky]
elif move == LEFT:
board[blankx][blanky], board[blankx + 1][blanky] = board[blankx + 1][blanky], board[blankx][blanky]
elif move == RIGHT:
board[blankx][blanky], board[blankx - 1][blanky] = board[blankx - 1][blanky], board[blankx][blanky]
# 該方法用在玩家用按鍵操作時檢查方塊是否可以移動
def isValidMove(board, move):
blankx, blanky = getBlankPosition(board) # 獲得空白方塊的位置
# 如果時向上滑動,就要確保空白方塊不在最下面的一行,否則就無法繼續和下面的方塊交換
# 同理,如果是向下,就確保空白方塊不在最上面
# 同理,如果是向左,就確保空白方塊不在最右面
# 同理,如果是向右,就確保空白方塊不在最左面
return (move == UP and blanky != len(board[0]) - 1) or \
(move == DOWN and blanky != 0) or \
(move == LEFT and blankx != len(board) - 1) or \
(move == RIGHT and blankx != 0)
# 隨機的移動一步
def getRandomMove(board, lastMove=None):
# 定義合法的移動集合
validMoves = [UP, DOWN, LEFT, RIGHT]
# 剔除掉不可用的方向
if lastMove == UP or not isValidMove(board, DOWN):
validMoves.remove(DOWN)
if lastMove == DOWN or not isValidMove(board, UP):
validMoves.remove(UP)
if lastMove == LEFT or not isValidMove(board, RIGHT):
validMoves.remove(RIGHT)
if lastMove == RIGHT or not isValidMove(board, LEFT):
validMoves.remove(LEFT)
# 從剩下可用的validMoves里隨機選取一個返回
return random.choice(validMoves)
# 獲得方塊左上角的像素坐標
def getLeftTopOfTile(tileX, tileY):
left = XMARGIN + (tileX * TILESIZE) + (tileX - 1)
top = YMARGIN + (tileY * TILESIZE) + (tileY - 1)
return (left, top)
# 根據像素坐標獲得方塊在board里的坐標
def getSpotClicked(board, x, y):
for tileX in range(len(board)):
for tileY in range(len(board[0])):
# 獲得每一個方塊的左上角的像素坐標
left, top = getLeftTopOfTile(tileX, tileY)
# 獲得方塊的Rect
tileRect = pygame.Rect(left, top, TILESIZE, TILESIZE)
# 判斷這個Rect是否包含點 (x,y)
if tileRect.collidepoint(x, y):
return (tileX, tileY)
return (None, None)
# 繪制方塊以及方塊上面的數字
def drawTile(tilex, tiley, number, adjx=0, adjy=0):
# draw a tile at board coordinates tilex and tiley, optionally a few
# pixels over (determined by adjx and adjy)
left, top = getLeftTopOfTile(tilex, tiley)
pygame.draw.rect(DISPLAYSURF, TILECOLOR, (left + adjx, top + adjy, TILESIZE, TILESIZE))
textSurf = BASICFONT.render(str(number), True, TEXTCOLOR)
textRect = textSurf.get_rect()
textRect.center = left + int(TILESIZE / 2) + adjx, top + int(TILESIZE / 2) + adjy
DISPLAYSURF.blit(textSurf, textRect)
# 創建text的對應的Surf和Rect
def makeText(text, color, bgcolor, top, left):
textSurf = BASICFONT.render(text, True, color, bgcolor)
textRect = textSurf.get_rect()
textRect.topleft = (top, left)
return (textSurf, textRect)
# 繪制整個Board
def drawBoard(board, message):
DISPLAYSURF.fill(BGCOLOR) # 繪制背景
if message: # 如果有消息,就繪制消息
textSurf, textRect = makeText(message, MESSAGECOLOR, BGCOLOR, 5, 5)
DISPLAYSURF.blit(textSurf, textRect)
#繪制每一個方塊
for tilex in range(len(board)):
for tiley in range(len(board[0])):
if board[tilex][tiley]:
drawTile(tilex, tiley, board[tilex][tiley])
# 繪制Board的邊框
left, top = getLeftTopOfTile(0, 0)
width = BOARDWIDTH * TILESIZE
height = BOARDHEIGHT * TILESIZE
pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (left - 5, top - 5, width + 11, height + 11), 4)
# 繪制右下角的三個按鈕
DISPLAYSURF.blit(RESET_SURF, RESET_RECT)
DISPLAYSURF.blit(NEW_SURF, NEW_RECT)
DISPLAYSURF.blit(SOLVE_SURF, SOLVE_RECT)
# 滑動的動畫
def slideAnimation(board, direction, message, animationSpeed):
blankx, blanky = getBlankPosition(board)# 獲得空白方塊的坐標
# 根據方向操作,計算 movex、movey(也就是與之交換的方塊的坐標)
if direction == UP:
movex = blankx
movey = blanky + 1
elif direction == DOWN:
movex = blankx
movey = blanky - 1
elif direction == LEFT:
movex = blankx + 1
movey = blanky
elif direction == RIGHT:
movex = blankx - 1
movey = blanky
drawBoard(board, message) # 顯示消息
baseSurf = DISPLAYSURF.copy() # 拷貝一份原來的surface
# 在baseSurf上繪制空白的方塊,覆蓋在移動的方塊上面
moveLeft, moveTop = getLeftTopOfTile(movex, movey)
pygame.draw.rect(baseSurf, BGCOLOR, (moveLeft, moveTop, TILESIZE, TILESIZE))
pygame.display.update()
FPSCLOCK.tick(FPS)
# 顯示移動的動畫
# range(1,5,2) #代表從1到5,間隔2(不包含5)
for i in range(0, TILESIZE, animationSpeed):
DISPLAYSURF.blit(baseSurf, (0, 0))
if direction == UP:
drawTile(movex, movey, board[movex][movey], 0, -i)
if direction == DOWN:
drawTile(movex, movey, board[movex][movey], 0, i)
if direction == LEFT:
drawTile(movex, movey, board[movex][movey], -i, 0)
if direction == RIGHT:
drawTile(movex, movey, board[movex][movey], i, 0)
pygame.display.update()
FPSCLOCK.tick(FPS)
# 生成新的拼圖(就是隨機打亂已經排序好的board,numSlides為打亂的步數)
def generateNewPuzzle(numSlides):
sequence = [] # 儲存打亂的步數的序列
board = getStartingBoard() # 獲得開始board的序列
drawBoard(board, '') # 繪制board
pygame.display.update() # 更新屏幕
pygame.time.wait(500) # 暫停500毫秒 for effect
lastMove = None
for i in range(numSlides):
move = getRandomMove(board, lastMove)# 隨機的移動一步
slideAnimation(board, move, 'Generating new puzzle...', int(TILESIZE / 3)) # 滑動的動畫
makeMove(board, move) # 交換空白方塊的位置
sequence.append(move) # 將這個步驟添加到sequence序列中
lastMove = move
return (board, sequence)
# 根據所有操作的的序列,恢復游戲
def resetAnimation(board, allMoves):
revAllMoves = allMoves[:] # 復制一份所有操作的的序列
revAllMoves.reverse() # 倒序排列
for move in revAllMoves: # 取出每一步,做相反的操作(就能恢復為原來的最初的序列)
if move == UP:
oppositeMove = DOWN
elif move == DOWN:
oppositeMove = UP
elif move == RIGHT:
oppositeMove = LEFT
elif move == LEFT:
oppositeMove = RIGHT
slideAnimation(board, oppositeMove, '', int(TILESIZE / 2)) # 滑動的動畫
makeMove(board, oppositeMove) # 交換空白方塊的位置
if __name__ == '__main__':
main()