《Making Games with Python & Pygame》实例Memory Puzzle代码及注释

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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值