赞
踩
- """
- 用到以下键:
- “x”和“y”——游戏在棋盘上的位置。0,0是左上角。
- 还有一个y可以设置的ROWABOVEBOARD行,表示它在棋盘上。
-
- 方向——四个常量变量之一,上、下、左、右,游戏前进的方向。
-
- 'imageNum' —— GEMIMAGES的整数索引,用于表示这个游戏用哪个图像。
- """
- import random, time, pygame, sys, copy
- from pygame.locals import *
-
- FPS = 30 # 每30秒更新屏幕
- WINDOWWIDTH = 600 # 窗口宽度
- WINDOWHEIGHT = 600 # 窗口高度
-
- BOARDWIDTH = 8 # 8列
- BOARDHEIGHT = 8 # 8行
- GEMIMAGESIZE = 64 # 每小格的宽度和高度
-
- NUMGEMIMAGES = 6 # 图片的类型,.png格式,从0开始命名 gem0.png
- assert NUMGEMIMAGES >= 5 # 最少5种图片
-
- NUMMATCHSOUNDS = 6 # 音乐的类型,.wav格式,从0开始命名 match0.wav
-
- MOVERATE = 25 #1到100,更大的num意味着更快的动作
- DEDUCTSPEED = 0.8 # 每减少1分扣减DEDUCTSPEED秒。
-
- # R G B
- PURPLE = (255, 0, 255)
- LIGHTBLUE = (255, 202, 255)
- BLUE = ( 77, 219, 255)
- RED = (255, 100, 100)
- BLACK = ( 0, 0, 0)
- BROWN = ( 85, 65, 0)
- HIGHLIGHTCOLOR = PURPLE # 游戏边界的颜色
- BGCOLOR = LIGHTBLUE # 屏幕上的背景色
- GRIDCOLOR = BLUE # 游戏板的颜色
- GAMEOVERCOLOR = RED #“游戏结束”文字的颜色
- GAMEOVERBGCOLOR = BLACK # “游戏结束”文字的颜色
- SCORECOLOR = BROWN # 得分文本的颜色
-
- # 从线到边的距离
- # 它被多次使用,因此在这里计算一次并存储在变量中
- XMARGIN = int((WINDOWWIDTH - GEMIMAGESIZE * BOARDWIDTH) / 2)
- YMARGIN = int((WINDOWHEIGHT - GEMIMAGESIZE * BOARDHEIGHT) / 2)
-
- # 方向常数
- UP = 'up'
- DOWN = 'down'
- LEFT = 'left'
- RIGHT = 'right'
-
- EMPTY_SPACE = -1 # 一个任意的非正值
- ROWABOVEBOARD = 'row above board' # 一个任意的非整数值
-
- def main():
- global FPSCLOCK, DISPLAYSURF, GEMIMAGES, GAMESOUNDS, BASICFONT, BOARDRECTS
-
- # 初始设置
- pygame.init()
- FPSCLOCK = pygame.time.Clock()
- DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
- pygame.display.set_caption('Gemgem')
- BASICFONT = pygame.font.Font('freesansbold.ttf', 36)
-
- # 加载图片
- GEMIMAGES = []
- for i in range(1, NUMGEMIMAGES+1):
- gemImage = pygame.image.load('gem%s.png' % i)
- if gemImage.get_size() != (GEMIMAGESIZE, GEMIMAGESIZE):
- gemImage = pygame.transform.smoothscale(gemImage, (GEMIMAGESIZE, GEMIMAGESIZE))
- GEMIMAGES.append(gemImage)
-
- # 加载声音
- GAMESOUNDS = {}
- GAMESOUNDS['bad swap'] = pygame.mixer.Sound('badswap.wav')
- GAMESOUNDS['match'] = []
- for i in range(NUMMATCHSOUNDS):
- GAMESOUNDS['match'].append(pygame.mixer.Sound('match%s.wav' % i))
-
-
- # 创建pygame,矩形对象,用于为每个板空间执行板坐标到像素坐标的转换
- BOARDRECTS = []
- for x in range(BOARDWIDTH):
- BOARDRECTS.append([])
- for y in range(BOARDHEIGHT):
- r = pygame.Rect((XMARGIN + (x * GEMIMAGESIZE),
- YMARGIN + (y * GEMIMAGESIZE),
- GEMIMAGESIZE,
- GEMIMAGESIZE))
- BOARDRECTS[x].append(r)
-
- while True:
- runGame()
-
-
- def runGame():
- # 运行游戏,当游戏结束时,这个函数返回
-
- # 初始化面板
- gameBoard = getBlankBoard()
- score = 0
- fillBoardAndAnimate(gameBoard, [], score) # Drop the initial gems.
-
- # 初始化变量
- firstSelectedGem = None
- lastMouseDownX = None
- lastMouseDownY = None
- gameIsOver = False
- lastScoreDeduction = time.time()
- clickContinueTextSurf = None
-
- while True: # 主要游戏循环
- clickedSpace = None
- for event in pygame.event.get(): # 事件处理循环
- if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
- pygame.quit()
- sys.exit()
- elif event.type == KEYUP and event.key == K_BACKSPACE:
- return # start a new game
-
- elif event.type == MOUSEBUTTONUP:
- if gameIsOver:
- return # 游戏结束后,点击开始一个新的游戏
-
- if event.pos == (lastMouseDownX, lastMouseDownY):
- # 此事件是鼠标单击,而不是鼠标拖动结束
- clickedSpace = checkForGemClick(event.pos)
- else:
- # 这是鼠标拖动结束
- firstSelectedGem = checkForGemClick((lastMouseDownX, lastMouseDownY))
- clickedSpace = checkForGemClick(event.pos)
- if not firstSelectedGem or not clickedSpace:
- # 如果不是有效拖动的一部分,取消选中这两个
- firstSelectedGem = None
- clickedSpace = None
- elif event.type == MOUSEBUTTONDOWN:
- # 这是鼠标点击或拖动的开始
- lastMouseDownX, lastMouseDownY = event.pos
-
- if clickedSpace and not firstSelectedGem:
- # 这是第一次点击宝石
- firstSelectedGem = clickedSpace
- elif clickedSpace and firstSelectedGem:
- #点击并选择了两个宝石,交换。
- firstSwappingGem, secondSwappingGem = getSwappingGems(gameBoard, firstSelectedGem, clickedSpace)
- if firstSwappingGem == None and secondSwappingGem == None:
- # 如果两者都不是,那么宝石就不是相邻的
- firstSelectedGem = None # 取消选择第一块宝石
- continue
-
- # 在屏幕上显示交换动画
- boardCopy = getBoardCopyMinusGems(gameBoard, (firstSwappingGem, secondSwappingGem))
- animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score)
-
- # 交换在面板上的数据结构上的宝石
- gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = secondSwappingGem['imageNum']
- gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = firstSwappingGem['imageNum']
-
- # 看看这是不是一个匹配的动作
- matchedGems = findMatchingGems(gameBoard)
- if matchedGems == []:
- # 不是一个匹配的移动;交换宝石
- GAMESOUNDS['bad swap'].play()
- animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score)
- gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = firstSwappingGem['imageNum']
- gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = secondSwappingGem['imageNum']
- else:
- # 是一个匹配的移动
- scoreAdd = 0
- while matchedGems != []:
- # 删除匹配的宝石,然后拉下板
-
- # points是一个dicts列表,它告诉fillBoardAndAnimate()在屏幕上显示文本以显示玩家获得多少点数。
- # 点数是一个列表,因为如果玩家得到多个匹配,那么就会出现多个点数文本。
-
- points = []
- for gemSet in matchedGems:
- scoreAdd += (10 + (len(gemSet) - 3) * 10)
- for gem in gemSet:
- gameBoard[gem[0]][gem[1]] = EMPTY_SPACE
- points.append({'points': scoreAdd,
- 'x': gem[0] * GEMIMAGESIZE + XMARGIN,
- 'y': gem[1] * GEMIMAGESIZE + YMARGIN})
- random.choice(GAMESOUNDS['match']).play()
- score += scoreAdd
-
- # 掉落新的宝石
- fillBoardAndAnimate(gameBoard, points, score)
-
- # 检查是否有新的匹配
- matchedGems = findMatchingGems(gameBoard)
- firstSelectedGem = None
-
- if not canMakeMove(gameBoard):
- gameIsOver = True
-
- # 画出面板
- DISPLAYSURF.fill(BGCOLOR)
- drawBoard(gameBoard)
- if firstSelectedGem != None:
- highlightSpace(firstSelectedGem['x'], firstSelectedGem['y'])
- if gameIsOver:
- if clickContinueTextSurf == None:
- #只呈现文本一次。在以后的迭代中,只需使用clickContinueTextSurf中已经存在的Surface对象
- clickContinueTextSurf = BASICFONT.render('Final Score: %s (Click to continue)' % (score), 1, GAMEOVERCOLOR, GAMEOVERBGCOLOR)
- clickContinueTextRect = clickContinueTextSurf.get_rect()
- clickContinueTextRect.center = int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)
- DISPLAYSURF.blit(clickContinueTextSurf, clickContinueTextRect)
- elif score > 0 and time.time() - lastScoreDeduction > DEDUCTSPEED:
- # 分数随时间而下降
- score -= 1
- lastScoreDeduction = time.time()
- drawScore(score)
- pygame.display.update()
- FPSCLOCK.tick(FPS)
-
-
- def getSwappingGems(board, firstXY, secondXY):
- # 如果两个宝石在(X, Y)坐标处的宝石相邻,那么它们的“方向”键被设置为适当的方向值,以便彼此交换
- # 否则,返回(None, None)
- firstGem = {'imageNum': board[firstXY['x']][firstXY['y']],
- 'x': firstXY['x'],
- 'y': firstXY['y']}
- secondGem = {'imageNum': board[secondXY['x']][secondXY['y']],
- 'x': secondXY['x'],
- 'y': secondXY['y']}
- highlightedGem = None
- if firstGem['x'] == secondGem['x'] + 1 and firstGem['y'] == secondGem['y']:
- firstGem['direction'] = LEFT
- secondGem['direction'] = RIGHT
- elif firstGem['x'] == secondGem['x'] - 1 and firstGem['y'] == secondGem['y']:
- firstGem['direction'] = RIGHT
- secondGem['direction'] = LEFT
- elif firstGem['y'] == secondGem['y'] + 1 and firstGem['x'] == secondGem['x']:
- firstGem['direction'] = UP
- secondGem['direction'] = DOWN
- elif firstGem['y'] == secondGem['y'] - 1 and firstGem['x'] == secondGem['x']:
- firstGem['direction'] = DOWN
- secondGem['direction'] = UP
- else:
- # 这些宝石不相邻,不能交换
- return None, None
- return firstGem, secondGem
-
-
- def getBlankBoard():
- # 创建并返回空白板数据结构
- board = []
- for x in range(BOARDWIDTH):
- board.append([EMPTY_SPACE] * BOARDHEIGHT)
- return board
-
-
- def canMakeMove(board):
- # 如果棋盘处于可以对其进行匹配移动的状态,则返回True。否则返回假
-
- # oneOffPatterns中的模式表示gem,它们的配置方式是只需要移动一次就可以创建一个三元组
-
- oneOffPatterns = (((0,1), (1,0), (2,0)),
- ((0,1), (1,1), (2,0)),
- ((0,0), (1,1), (2,0)),
- ((0,1), (1,0), (2,1)),
- ((0,0), (1,0), (2,1)),
- ((0,0), (1,1), (2,1)),
- ((0,0), (0,2), (0,3)),
- ((0,0), (0,1), (0,3)))
-
- # x和y变量在黑板上的每个空间上迭代。如果我们使用+表示当前在黑板上迭代的空间,那么这个模式:(0,1),(1,0),(2,0))表示相同的
-
- # 宝石是这样设置的:
- #
- # +A
- # B
- # C
- #
- # 即,gem A与+的偏移量为(0,1),gem B与(1,0)的偏移量为(2,0)。在这种情况下,gem A可以被交换到左边以形成一个垂直的三排三连音
- #
- # 有八种可能的方式使宝石远离形成一个三元组,因此oneOffPattern有8种模式
-
- for x in range(BOARDWIDTH):
- for y in range(BOARDHEIGHT):
- for pat in oneOffPatterns:
- # 检查每个可能的模式“匹配在下一步”,看看是否可以做一个可能的行动
-
- if (getGemAt(board, x+pat[0][0], y+pat[0][1]) == \
- getGemAt(board, x+pat[1][0], y+pat[1][1]) == \
- getGemAt(board, x+pat[2][0], y+pat[2][1]) != None) or \
- (getGemAt(board, x+pat[0][1], y+pat[0][0]) == \
- getGemAt(board, x+pat[1][1], y+pat[1][0]) == \
- getGemAt(board, x+pat[2][1], y+pat[2][0]) != None):
- return True # return True the first time you find a pattern
- return False
-
-
- def drawMovingGem(gem, progress):
- # 画一个宝石沿着它的“方向”键指示的方向滑动。进度参数是从0(刚开始)到100(幻灯片完成)的数字
-
- movex = 0
- movey = 0
- progress *= 0.01
-
- if gem['direction'] == UP:
- movey = -int(progress * GEMIMAGESIZE)
- elif gem['direction'] == DOWN:
- movey = int(progress * GEMIMAGESIZE)
- elif gem['direction'] == RIGHT:
- movex = int(progress * GEMIMAGESIZE)
- elif gem['direction'] == LEFT:
- movex = -int(progress * GEMIMAGESIZE)
-
- basex = gem['x']
- basey = gem['y']
- if basey == ROWABOVEBOARD:
- basey = -1
-
- pixelx = XMARGIN + (basex * GEMIMAGESIZE)
- pixely = YMARGIN + (basey * GEMIMAGESIZE)
- r = pygame.Rect( (pixelx + movex, pixely + movey, GEMIMAGESIZE, GEMIMAGESIZE) )
- DISPLAYSURF.blit(GEMIMAGES[gem['imageNum']], r)
-
-
- def pullDownAllGems(board):
- # 将木板上的宝石拉到底部,以填补任何空缺
- for x in range(BOARDWIDTH):
- gemsInColumn = []
- for y in range(BOARDHEIGHT):
- if board[x][y] != EMPTY_SPACE:
- gemsInColumn.append(board[x][y])
- board[x] = ([EMPTY_SPACE] * (BOARDHEIGHT - len(gemsInColumn))) + gemsInColumn
-
-
- def getGemAt(board, x, y):
- if x < 0 or y < 0 or x >= BOARDWIDTH or y >= BOARDHEIGHT:
- return None
- else:
- return board[x][y]
-
-
- def getDropSlots(board):
- # 为每个列创建一个“drop slot”,并用该列缺少的一些gem填充该slot。这个函数假设宝石的重力已经下降
-
- boardCopy = copy.deepcopy(board)
- pullDownAllGems(boardCopy)
-
- dropSlots = []
- for i in range(BOARDWIDTH):
- dropSlots.append([])
-
- # 计算黑板上每一列的空格数
- for x in range(BOARDWIDTH):
- for y in range(BOARDHEIGHT-1, -1, -1): # 从底部开始,向上
- if boardCopy[x][y] == EMPTY_SPACE:
- possibleGems = list(range(len(GEMIMAGES)))
- for offsetX, offsetY in ((0, -1), (1, 0), (0, 1), (-1, 0)):
- # 缩小可能的宝石的范围,我们应该把它们放在空白的地方,这样当它们掉落的时候,我们就不会把两个相同的宝石放在一起
- neighborGem = getGemAt(boardCopy, x + offsetX, y + offsetY)
- if neighborGem != None and neighborGem in possibleGems:
- possibleGems.remove(neighborGem)
-
- newGem = random.choice(possibleGems)
- boardCopy[x][y] = newGem
- dropSlots[x].append(newGem)
- return dropSlots
-
-
- def findMatchingGems(board):
- gemsToRemove = [] # a list of lists of gems in matching triplets that should be removed
- boardCopy = copy.deepcopy(board)
-
- # 循环遍历每个空间,检查3个相邻的相同的宝石
- for x in range(BOARDWIDTH):
- for y in range(BOARDHEIGHT):
- # 寻找水平匹配
- if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x + 1, y) == getGemAt(boardCopy, x + 2, y) and getGemAt(boardCopy, x, y) != EMPTY_SPACE:
- targetGem = boardCopy[x][y]
- offset = 0
- removeSet = []
- while getGemAt(boardCopy, x + offset, y) == targetGem:
- #继续检查一行中是否有超过3个gem
- removeSet.append((x + offset, y))
- boardCopy[x + offset][y] = EMPTY_SPACE
- offset += 1
- gemsToRemove.append(removeSet)
-
- # 寻找垂直匹配
- if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x, y + 1) == getGemAt(boardCopy, x, y + 2) and getGemAt(boardCopy, x, y) != EMPTY_SPACE:
- targetGem = boardCopy[x][y]
- offset = 0
- removeSet = []
- while getGemAt(boardCopy, x, y + offset) == targetGem:
- # 继续检查,以防一行中有超过3个gem
- removeSet.append((x, y + offset))
- boardCopy[x][y + offset] = EMPTY_SPACE
- offset += 1
- gemsToRemove.append(removeSet)
-
- return gemsToRemove
-
-
- def highlightSpace(x, y):
- pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, BOARDRECTS[x][y], 4)
-
-
- def getDroppingGems(board):
- #找到所有在他们下面有一个空空间的宝石
- boardCopy = copy.deepcopy(board)
- droppingGems = []
- for x in range(BOARDWIDTH):
- for y in range(BOARDHEIGHT - 2, -1, -1):
- if boardCopy[x][y + 1] == EMPTY_SPACE and boardCopy[x][y] != EMPTY_SPACE:
- # 如果不是空的,这个空间会下降,但是下面的空间是空的
- droppingGems.append( {'imageNum': boardCopy[x][y], 'x': x, 'y': y, 'direction': DOWN} )
- boardCopy[x][y] = EMPTY_SPACE
- return droppingGems
-
-
- def animateMovingGems(board, gems, pointsText, score):
- # pointsText是一个带有'x'、'y'和'points'键的字典
- progress = 0 #进展在0代表开始,100代表完成
- while progress < 100: # 动画循环
- DISPLAYSURF.fill(BGCOLOR)
- drawBoard(board)
- for gem in gems: # 布置每一个宝石
- drawMovingGem(gem, progress)
- drawScore(score)
- for pointText in pointsText:
- pointsSurf = BASICFONT.render(str(pointText['points']), 1, SCORECOLOR)
- pointsRect = pointsSurf.get_rect()
- pointsRect.center = (pointText['x'], pointText['y'])
- DISPLAYSURF.blit(pointsSurf, pointsRect)
-
- pygame.display.update()
- FPSCLOCK.tick(FPS)
- progress += MOVERATE # 为下一帧的动画进行更多的进展
-
-
- def moveGems(board, movingGems):
- # 移动宝石是一个带有键x, y,方向,imageNum的字典列表
- for gem in movingGems:
- if gem['y'] != ROWABOVEBOARD:
- board[gem['x']][gem['y']] = EMPTY_SPACE
- movex = 0
- movey = 0
- if gem['direction'] == LEFT:
- movex = -1
- elif gem['direction'] == RIGHT:
- movex = 1
- elif gem['direction'] == DOWN:
- movey = 1
- elif gem['direction'] == UP:
- movey = -1
- board[gem['x'] + movex][gem['y'] + movey] = gem['imageNum']
- else:
- # 宝石位于板的上方(新的宝石来自板的上方)
- board[gem['x']][0] = gem['imageNum'] # move to top row
-
-
- def fillBoardAndAnimate(board, points, score):
- dropSlots = getDropSlots(board)
- while dropSlots != [[]] * BOARDWIDTH:
- # 只要有更多的宝石掉落,做掉落的动画
- movingGems = getDroppingGems(board)
- for x in range(len(dropSlots)):
- if len(dropSlots[x]) != 0:
- # 使每个槽中最低的宝石开始向下移动
- movingGems.append({'imageNum': dropSlots[x][0], 'x': x, 'y': ROWABOVEBOARD, 'direction': DOWN})
-
- boardCopy = getBoardCopyMinusGems(board, movingGems)
- animateMovingGems(boardCopy, movingGems, points, score)
- moveGems(board, movingGems)
-
- # 通过删除之前的最低的gem,从下拉槽中删除下一行gems是最低的
-
- for x in range(len(dropSlots)):
- if len(dropSlots[x]) == 0:
- continue
- board[x][0] = dropSlots[x][0]
- del dropSlots[x][0]
-
-
- def checkForGemClick(pos):
- # 查看鼠标点击是否在面板上
- for x in range(BOARDWIDTH):
- for y in range(BOARDHEIGHT):
- if BOARDRECTS[x][y].collidepoint(pos[0], pos[1]):
- return {'x': x, 'y': y}
- return None # Click不在面板上
-
-
- def drawBoard(board):
- for x in range(BOARDWIDTH):
- for y in range(BOARDHEIGHT):
- pygame.draw.rect(DISPLAYSURF, GRIDCOLOR, BOARDRECTS[x][y], 1)
- gemToDraw = board[x][y]
- if gemToDraw != EMPTY_SPACE:
- DISPLAYSURF.blit(GEMIMAGES[gemToDraw], BOARDRECTS[x][y])
-
-
- def getBoardCopyMinusGems(board, gems):
- # 创建并返回已传递的板数据结构的副本,并从其中删除“gems”列表中的gems
- # Gems是一个dicts列表,带有键x, y,方向,imageNum
-
- boardCopy = copy.deepcopy(board)
-
- # 从面板板数据结构拷贝中删除一些宝石
- for gem in gems:
- if gem['y'] != ROWABOVEBOARD:
- boardCopy[gem['x']][gem['y']] = EMPTY_SPACE
- return boardCopy
-
-
- def drawScore(score):
- scoreImg = BASICFONT.render(str(score), 1, SCORECOLOR)
- scoreRect = scoreImg.get_rect()
- scoreRect.bottomleft = (10, WINDOWHEIGHT - 6)
- DISPLAYSURF.blit(scoreImg, scoreRect)
-
-
- if __name__ == '__main__':
- main()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。