import pygame,random,sys
from pygame.locals import *
# 笔者注释中的“盒子”,原文注释有时用“icons”,因为具体某个位置,既有可能绘制icon,
# 也可能绘制矩形,用盒子来表达位置,似乎比icon更贴切点
FPS=30 # the general speed of the program
WINDOWWIDH=640 #窗口宽度像素数,pygame绘图用
WINDOWHEIGHT=480 #窗口高度像素数,pygame绘图用
REVEALSPEED=8 # speed boxes' sliding reveals and covers
BOXSIZE=40 # size of box height & width in pixels
GAPSIZE=10 # size of gap between boxes in pixels
BOARDWIDTH=10 # 用途是规定创建盒子的列数
BOARDHEIGHT=7 # 创建盒子的行数
# assert
XMARGIN=int((WINDOWWIDH-(BOARDWIDTH*(BOXSIZE+GAPSIZE)))/2) # 除以2是因为两侧各一
YMARGIN=int((WINDOWHEIGHT-(BOARDHEIGHT*(BOXSIZE+GAPSIZE)))/2)
# R G B
GRAY=(100,100,100)
NAVYBLUE=(60,60,100)
WHITE=(255,255,255)
RED=(255,0,0)
GREEN=(0,255,0)
BLUE=(0,0,255)
YELLOW=(255,255,0)
ORANGE=(255,128,0)
PURPLE=(255,0,255)
CYAN=(0,255,255)
BGCOLOR=NAVYBLUE
LIGHTBGCOLOR=GRAY
BOXCOLOR=WHITE
HTGHLIGHTCOLOR=BLUE
DONUT='donut'
SQUARE='square'
DIAMOND='diamond'
LINES='lines'
OVAL='oval'
ALLCOLORS=(RED,GREEN,BLUE,YELLOW,ORANGE,PURPLE,CYAN)
ALLSHAPES=(DONUT,SQUARE,DIAMOND,LINES,OVAL)
# assert
def main():
global FPSCLOCK,DISPLAYSURF
pygame.init()
FPSCLOCK=pygame.time.Clock()
DISPLAYSURF=pygame.display.set_mode((WINDOWWIDH,WINDOWHEIGHT))
mousex=0
mousey=0
pygame.display.set_caption('Memory Game')
mainBoard=getRandomizedBoard()
# mainBoxes是一个BOARDWIDTH行、[val]*BOARDHEIGHT的列表
# 元素的初始值全是(shape,color)组合的某个随机值
# BOARDWIDTH以盒子的数量为计数单位,不是像素,因为最终要翻开盒子
revealedBoxes=generateRevealedBoxesData(False)
# revealedBoxes是一个BOARDWIDTH行、[val]*BOARDHEIGHT的列表
# 元素的初始值全是0
# 此处用False代替数字0
# mainBoxes和revealedBoxes只是定义了两个数据结构,在内存中保存数据,目前与显示无关
firstSelection=None
# stores the (x,y) of the first box clicked
DISPLAYSURF.fill(BGCOLOR)
startGameAnimation(mainBoard)
while True:
mouseClicked=False
DISPLAYSURF.fill(BGCOLOR)
drawBoard(mainBoard,revealedBoxes)
for et in pygame.event.get():
if et.type==QUIT or (et.type==KEYUP and et.key==K_ESCAPE):
pygame.quit()
sys.exit()
elif et.type==MOUSEMOTION:
mousex,mousey=et.pos
elif et.type==MOUSEBUTTONUP:
mousex,mousey=et.pos
mouseClicked=True
boxx,boxy=getBoxAtPixel(mousex,mousey)
if boxx != None and boxy != None:
# 说明此时鼠标在一个盒子上
if not revealedBoxes[boxx][boxy]:
# 如果原有元素的值是0,即盒子没有被翻过来
drawHighlightBox(boxx,boxy)
# 此时鼠标在盒子上面,盒子高亮变色
if not revealedBoxes[boxx][boxy] and mouseClicked:
revealBoxesAnimation(mainBoard,[(boxx,boxy)])
# [(boxx,boxy)]的思路是重点
revealedBoxes[boxx][boxy]=True
# 更新选择蒙版
if firstSelection==None:
firstSelection=(boxx,boxy)
# 这个flag为后面比对两个icon是否相同做准备
else:
icon1shape,icon1color=getShapeAndColor(mainBoard,firstSelection[0],firstSelection[1])
icon2shape,icon2color=getShapeAndColor(mainBoard,boxx,boxy)
if icon1color != icon2color or icon1shape != icon2shape:
pygame.time.wait(1000)
coverBoxesAnimation(mainBoard,[(firstSelection[0],firstSelection[1]),(boxx,boxy)])
revealedBoxes[firstSelection[0]][firstSelection[1]]=False
revealedBoxes[boxx][boxy]=False
elif hasWon(revealedBoxes):
gameWonAnimation(mainBoard)
pygame.time.wait(2000)
# 既然赢了,重新开局
mainBoard=getRandomizedBoard()
revealedBoxes=generateRevealedBoxesData(False)
drawBoard(mainBoard,revealedBoxes)
pygame.display.update()
pygame.time.wait(1000)
startGameAnimation(mainBoard)
firstSelection=None
pygame.display.update()
FPSCLOCK.tick(FPS)
def hasWon(revealedBoxes):
"""retrun true if all the boxes have been revealed"""
for i in revealedBoxes:
if False in i:
return False
return True
def gameWonAnimation(board):
"""flash 2 kind bgcolor when won"""
coveredBoxes=generateRevealedBoxesData(True)
color1=LIGHTBGCOLOR
color2=BGCOLOR
for i in range(13):
color1,color2=color2,color1
# 注意赋值语句右侧是2,1,说明交换了颜色,从而实现闪烁的效果
DISPLAYSURF.fill(color1)
drawBoard(board,coveredBoxes)
pygame.display.update()
pygame.time.wait(300)
def drawHighlightBox(boxx,boxy):
left,top=leftTopCoordsOfBox(boxx,boxy)
pygame.draw.rect(DISPLAYSURF,HTGHLIGHTCOLOR,(left-5,top-5,BOXSIZE+10,BOXSIZE+10),4)
def getBoxAtPixel(x,y):
"""用与盒子同样大小的矩形,测试像素点是否在矩形内,如在,返回盒子坐标"""
for boxx in range(BOARDWIDTH):
for boxy in range(BOARDHEIGHT):
left,top=leftTopCoordsOfBox(boxx,boxy)
boxRect=pygame.Rect(left,top,BOXSIZE,BOXSIZE)
# Rect() 方法来创建一个指定位置、大小的矩形区域
if boxRect.collidepoint(x,y):
# 测试点是否在矩形内
return(boxx,boxy)
return(None,None)
# 阅
def generateRevealedBoxesData(val):
"""revealedBoxes是一个BOARDWIDTH行、[val]*BOARDHEIGHT的列表,用参数val填充每个元素"""
revealedBoxes=[]
for i in range(BOARDWIDTH):
# append中用[val]保证结果是二维
revealedBoxes.append([val]*BOARDHEIGHT)
return revealedBoxes
# 阅
def getRandomizedBoard():
"""get a list of every possible shape in every possible color"""
# 虽然包含了颜色-形状组合,但没有绘制到surface上,只是数据,窗口中还不可见
# icons 是一个颜色和形状的组合全集,不是与窗口宽高无关
icons=[]
for color in ALLCOLORS:
for shape in ALLSHAPES:
icons.append((shape,color))
# shuffle打乱顺序,直接替换原数据
random.shuffle(icons)
# calculate how many icons are needed
# BOARD的两个常量与创建icons时用的ALLCOLORS和ALLSHAPES无关
numIconsUsed=int(BOARDWIDTH*BOARDHEIGHT/2)
# make 2 for each
icons=icons[:numIconsUsed]*2
# 先除2再乘2是为了确保没个形状出现偶数次,保证游戏能通关
random.shuffle(icons)
# create the board data structure, with randomly placed icons
board=[]
for x in range(BOARDWIDTH):
column=[]
for y in range(BOARDHEIGHT):
column.append(icons[0])
del icons[0]
# 在一次循环中将当前的icon[0]值赋值给board的某行某列(即某单元格),后弹出
# 第二次循环时用新的icon[0]赋值
board.append(column)
return board
# 结果是用形状-颜色组合填充的列表
# 设计思路是先创建一个中间集合,包含需要的组合元素,再用中间集合填充目标变量
# 阅
def startGameAnimation(board):
# board是一个填满颜色-形状组合的列表
# randomly reveal the boxes 8 at a time
# revealedBoxes是一个BOARDWIDTH行、[val]*BOARDHEIGHT的列表
coveredBoxes=generateRevealedBoxesData(False)
# 得到一个用数字0填充的列表,与board同维数
# 因为新建更容易,且所有元素都是0,所以没有将它作为函数参数导入
boxes=[]
# boxes 用来记录每个盒子的坐标(盒子维度,不是像素)
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
boxes.append((x,y))
random.shuffle(boxes)
# boxGroups是由list(长度为8)组成的list,仍是只是一个数据结构,目前未用来出图
# 打乱顺序后,8个一组,注意这不是用于排列原始图标的,
boxGroups=splitIntoGroupsOf(8,boxes)
drawBoard(board,coveredBoxes)
# board是通过形参传入的父环境里的值,coveredBox则是本函数内部的变量值
for boxGroup in boxGroups:
revealBoxesAnimation(board,boxGroup)
coverBoxesAnimation(board,boxGroup)
def revealBoxesAnimation(board,boxesToReveal):
for coverage in range(BOXSIZE,(-REVEALSPEED)-1,-REVEALSPEED):
drawBoxCovers(board,boxesToReveal,coverage)
def drawBoxCovers(board,boxes,coverage):
"""draws boxes being covered/revealed"""
# boxes is a list of 2-item lists,which have the x & y spot of the box
for box in boxes:
left,top=leftTopCoordsOfBox(box[0],box[1])
pygame.draw.rect(DISPLAYSURF,BGCOLOR,(left,top,BOXSIZE,BOXSIZE))
shape,color=getShapeAndColor(board,box[0],box[1])
drawIcon(shape,color,box[0],box[1])
if coverage>0:
# only draw the cover if there is an coverage
pygame.draw.rect(DISPLAYSURF,BOXCOLOR,(left,top,coverage,BOXSIZE))
pygame.display.update()
FPSCLOCK.tick(FPS)
def coverBoxesAnimation(board,boxesToCover):
for coverage in range(0,BOXSIZE+REVEALSPEED,REVEALSPEED):
drawBoxCovers(board,boxesToCover,coverage)
def splitIntoGroupsOf(groupsize,theList):
""" splits a list into a list of list,where the inner lists """
# have at most groupsize number of items
result=[]
# range(a,b,c)中的c是a增长到b的步长,不是输出a,b,c三个数
# i的结果是从0到len(theList)、以groupsize为步长的各个数
# append()中的元素是list,所以结果就是 a list of list
for i in range(0,len(theList),groupsize):
result.append(theList[i:i+groupsize])
return result
# 阅
def drawBoard(board,revealed):
""" mainboard之上是条件判断层,符合条件,根据mainboard中的信息出图,不符合,出矩形 """
for boxx in range(BOARDWIDTH):
# BOARDWIDTH==10,注意这不是像素数,而是盒子的列数。窗口的像素数是640
for boxy in range(BOARDHEIGHT): #7,同上
left,top=leftTopCoordsOfBox(boxx,boxy)
# boxx虽然用x,y,但不是像素的坐标,是为绘制盒子而创建的坐标
# 要通过pygame绘图,需要先转成像素坐标
if not revealed[boxx][boxy]:
# draw a covered box
# 用形参revealed传入的List中的值全是0,所以全部绘制
# 如果其中某个元素改成1,就会被排除在外,该位置不会绘制矩形
pygame.draw.rect(DISPLAYSURF,BOXCOLOR,(left,top,BOXSIZE,BOXSIZE))
else:
# draw the revealed icon
# 符合if条件的绘制矩形,不符合的,绘制icon
shape,color=getShapeAndColor(board,boxx,boxy)
drawIcon(shape,color,boxx,boxy)
# board坐标用于设计盒子,本函数负责转换成真实像素坐标,方便出图
def leftTopCoordsOfBox(boxx,boxy):
"""convert board coordinates to pixel coordinate"""
# 虽然用x,y,但不是像素的坐标,是人为规定的range
# 用下面的关系式计算的结果才是像素数,才能用pygame找到准确位置
# 作者把盒子和后面的间隔作为一个整体,所以最右面的margin比左边的大一点
left=boxx*(BOXSIZE+GAPSIZE)+XMARGIN
top=boxy*(BOXSIZE+GAPSIZE)+YMARGIN
return(left,top)
def getShapeAndColor(board,boxx,boxy):
# shape value for x,y spot is stored in board[x][y][0]
# color value for x,y spot is stored in board[x][y][1]
return board[boxx][boxy][0],board[boxx][boxy][1]
def drawIcon(shape,color,boxx,boxy):
quater=int(BOXSIZE*0.25)
half=int(BOXSIZE*0.5)
# get pixel coords from board coords
left,top=leftTopCoordsOfBox(boxx,boxy)
# draw the shape
if shape==DONUT:
pygame.draw.circle(DISPLAYSURF,color,(left+half,top+half),half-5)
pygame.draw.circle(DISPLAYSURF,BGCOLOR,(left+half,top+half),quater-5)
elif shape==SQUARE:
pygame.draw.rect(DISPLAYSURF,color,(left+quater,top+quater,BOXSIZE-half,BOXSIZE-half))
elif shape==DIAMOND:
pygame.draw.polygon(DISPLAYSURF,color,((left+half,top),(left+BOXSIZE-1,top+half),(left+half,top+BOXSIZE-1),(left,top+half)))
elif shape==LINES:
for i in range(0,BOXSIZE,4):
pygame.draw.line(DISPLAYSURF,color,(left,top+i),(left+i,top))
pygame.draw.line(DISPLAYSURF,color,(left+i,top+BOXSIZE-1),(left+BOXSIZE-1,top+i))
elif shape==OVAL:
pygame.draw.ellipse(DISPLAYSURF,color,(left,top+quater,BOXSIZE,half))
if __name__ == '__main__':
main()