Pygame - Hướng dẫn làm game Puzzle Memory

04/01/2022   PyGame
Pygame - Hướng dẫn làm game Puzzle Memory
Nội dung bài viết
1. Cách chơi Puzzle Memory |
1. Cách chơi Puzzle Memory
Trong trò chơi Puzzle Puzzle, một số biểu tượng được bao phủ bởi các hộp màu trắng. Có hai trong số mỗi biểu tượng. Người chơi có thể nhấp vào hai hộp để xem biểu tượng nào đằng sau chúng. Nếu các biểu tượng khớp với nhau, thì các hộp đó vẫn chưa được khám phá. Người chơi chiến thắng khi tất cả các ô trên bảng được phát hiện. Để cung cấp cho người chơi một gợi ý, các hộp nhanh chóng được phát hiện một lần vào đầu trò chơi.
2. Soucre game Memory Puzzle có thể tải tại đây Puzzle_Memory.py
1. # Memory Puzzle 2. # By Al Sweigart al@inventwithpython.com 3. # http://inventwithpython.com/pygame 4. # Released under a "Simplified BSD" license 5. 6. import random, pygame, sys 7. from pygame.locals import * 8. 9. FPS = 30 # frames per second, the general speed of the program 10. WINDOWWIDTH = 640 # size of window's width in pixels 11. WINDOWHEIGHT = 480 # size of windows' height in pixels 12. REVEALSPEED = 8 # speed boxes' sliding reveals and covers 13. BOXSIZE = 40 # size of box height & width in pixels 14. GAPSIZE = 10 # size of gap between boxes in pixels 15. BOARDWIDTH = 10 # number of columns of icons 16. BOARDHEIGHT = 7 # number of rows of icons 17. assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.' 18. XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2) 19. YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2) 20. 21. # R G B 22. GRAY = (100, 100, 100) 23. NAVYBLUE = ( 60, 60, 100) 24. WHITE = (255, 255, 255) 25. RED = (255, 0, 0) 26. GREEN = ( 0, 255, 0) 27. BLUE = ( 0, 0, 255) 28. YELLOW = (255, 255, 0) 29. ORANGE = (255, 128, 0) 30. PURPLE = (255, 0, 255) 31. CYAN = ( 0, 255, 255) 32. 33. BGCOLOR = NAVYBLUE 34. LIGHTBGCOLOR = GRAY 35. BOXCOLOR = WHITE 36. HIGHLIGHTCOLOR = BLUE 37. 38. DONUT = 'donut' 39. SQUARE = 'square' 40. DIAMOND = 'diamond' 41. LINES = 'lines' 42. OVAL = 'oval' 43. 44. ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN) 45. ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL) 46. assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined." 47. 48. def main(): 49. global FPSCLOCK, DISPLAYSURF 50. pygame.init() 51. FPSCLOCK = pygame.time.Clock() 52. DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 53. 54. mousex = 0 # used to store x coordinate of mouse event 55. mousey = 0 # used to store y coordinate of mouse event 56. pygame.display.set_caption('Memory Game') 57. 58. mainBoard = getRandomizedBoard() 59. revealedBoxes = generateRevealedBoxesData(False) 60. 61. firstSelection = None # stores the (x, y) of the first box clicked. 62. 63. DISPLAYSURF.fill(BGCOLOR) 64. startGameAnimation(mainBoard) 65. 66. while True: # main game loop 67. mouseClicked = False 68. 69. DISPLAYSURF.fill(BGCOLOR) # drawing the window 70. drawBoard(mainBoard, revealedBoxes) 71. 72. for event in pygame.event.get(): # event handling loop 73. if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): 74. pygame.quit() 75. sys.exit() 76. elif event.type == MOUSEMOTION: 77. mousex, mousey = event.pos 78. elif event.type == MOUSEBUTTONUP: 79. mousex, mousey = event.pos 80. mouseClicked = True 81. 82. boxx, boxy = getBoxAtPixel(mousex, mousey) 83. if boxx != None and boxy != None: 84. # The mouse is currently over a box. 85. if not revealedBoxes[boxx][boxy]: 86. drawHighlightBox(boxx, boxy) 87. if not revealedBoxes[boxx][boxy] and mouseClicked: 88. revealBoxesAnimation(mainBoard, [(boxx, boxy)]) 89. revealedBoxes[boxx][boxy] = True # set the box as "revealed" 90. if firstSelection == None: # the current box was the first box clicked 91. firstSelection = (boxx, boxy) 92. else: # the current box was the second box clicked 93. # Check if there is a match between the two icons. 94. icon1shape, icon1color = getShapeAndColor(mainBoard, firstSelection[0], firstSelection[1]) 95. icon2shape, icon2color = getShapeAndColor(mainBoard, boxx, boxy) 96. 97. if icon1shape != icon2shape or icon1color != icon2color: 98. # Icons don't match. Re-cover up both selections. 99. pygame.time.wait(1000) # 1000 milliseconds = 1 sec 100. coverBoxesAnimation(mainBoard, [(firstSelection[0], firstSelection[1]), (boxx, boxy)]) 101. revealedBoxes[firstSelection[0]][firstSelection [1]] = False 102. revealedBoxes[boxx][boxy] = False 103. elif hasWon(revealedBoxes): # check if all pairs found 104. gameWonAnimation(mainBoard) 105. pygame.time.wait(2000) 106. 107. # Reset the board 108. mainBoard = getRandomizedBoard() 109. revealedBoxes = generateRevealedBoxesData(False) 110. 111. # Show the fully unrevealed board for a second. 112. drawBoard(mainBoard, revealedBoxes) 113. pygame.display.update() 114. pygame.time.wait(1000) 115. 116. # Replay the start game animation. 117. startGameAnimation(mainBoard) 118. firstSelection = None # reset firstSelection variable 119. 120. # Redraw the screen and wait a clock tick. 121. pygame.display.update() 122. FPSCLOCK.tick(FPS) 123. 124. 125. def generateRevealedBoxesData(val): 126. revealedBoxes = [] 127. for i in range(BOARDWIDTH): 128. revealedBoxes.append([val] * BOARDHEIGHT) 129. return revealedBoxes 130. 131. 132. def getRandomizedBoard(): 133. # Get a list of every possible shape in every possible color. 134. icons = [] 135. for color in ALLCOLORS: 136. for shape in ALLSHAPES: 137. icons.append( (shape, color) ) 138. 139. random.shuffle(icons) # randomize the order of the icons list 140. numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed 141. icons = icons[:numIconsUsed] * 2 # make two of each 142. random.shuffle(icons) 143. 144. # Create the board data structure, with randomly placed icons. 145. board = [] 146. for x in range(BOARDWIDTH): 147. column = [] 148. for y in range(BOARDHEIGHT): 149. column.append(icons[0]) 150. del icons[0] # remove the icons as we assign them 151. board.append(column) 152. return board 153. 154. 155. def splitIntoGroupsOf(groupSize, theList): 156. # splits a list into a list of lists, where the inner lists have at 157. # most groupSize number of items. 158. result = [] 159. for i in range(0, len(theList), groupSize): 160. result.append(theList[i:i + groupSize]) 161. return result 162. 163. 164. def leftTopCoordsOfBox(boxx, boxy): 165. # Convert board coordinates to pixel coordinates 166. left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN 167. top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN 168. return (left, top) 169. 170. 171. def getBoxAtPixel(x, y): 172. for boxx in range(BOARDWIDTH): 173. for boxy in range(BOARDHEIGHT): 174. left, top = leftTopCoordsOfBox(boxx, boxy) 175. boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE) 176. if boxRect.collidepoint(x, y): 177. return (boxx, boxy) 178. return (None, None) 179. 180. 181. def drawIcon(shape, color, boxx, boxy): 182. quarter = int(BOXSIZE * 0.25) # syntactic sugar 183. half = int(BOXSIZE * 0.5) # syntactic sugar 184. 185. left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords 186. # Draw the shapes 187. if shape == DONUT: 188. pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5) 189. pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5) 190. elif shape == SQUARE: 191. pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half)) 192. elif shape == DIAMOND: 193. pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half))) 194. elif shape == LINES: 195. for i in range(0, BOXSIZE, 4): 196. pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top)) 197. pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i)) 198. elif shape == OVAL: 199. pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half)) 200. 201. 202. def getShapeAndColor(board, boxx, boxy): 203. # shape value for x, y spot is stored in board[x][y][0] 204. # color value for x, y spot is stored in board[x][y][1] 205. return board[boxx][boxy][0], board[boxx][boxy][1] 206. 207. 208. def drawBoxCovers(board, boxes, coverage): 209. # Draws boxes being covered/revealed. "boxes" is a list 210. # of two-item lists, which have the x & y spot of the box. 211. for box in boxes: 212. left, top = leftTopCoordsOfBox(box[0], box[1]) 213. pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE)) 214. shape, color = getShapeAndColor(board, box[0], box[1]) 215. drawIcon(shape, color, box[0], box[1]) 216. if coverage > 0: # only draw the cover if there is an coverage 217. pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE)) 218. pygame.display.update() 219. FPSCLOCK.tick(FPS) 220. 221. 222. def revealBoxesAnimation(board, boxesToReveal): 223. # Do the "box reveal" animation. 224. for coverage in range(BOXSIZE, (-REVEALSPEED) - 1, - REVEALSPEED): 225. drawBoxCovers(board, boxesToReveal, coverage) 226. 227. 228. def coverBoxesAnimation(board, boxesToCover): 229. # Do the "box cover" animation. 230. for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED): 231. drawBoxCovers(board, boxesToCover, coverage) 232. 233. 234. def drawBoard(board, revealed): 235. # Draws all of the boxes in their covered or revealed state. 236. for boxx in range(BOARDWIDTH): 237. for boxy in range(BOARDHEIGHT): 238. left, top = leftTopCoordsOfBox(boxx, boxy) 239. if not revealed[boxx][boxy]: 240. # Draw a covered box. 241. pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE)) 242. else: 243. # Draw the (revealed) icon. 244. shape, color = getShapeAndColor(board, boxx, boxy) 245. drawIcon(shape, color, boxx, boxy) 246. 247. 248. def drawHighlightBox(boxx, boxy): 249. left, top = leftTopCoordsOfBox(boxx, boxy) 250. pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5, BOXSIZE + 10, BOXSIZE + 10), 4) 251. 252. 253. def startGameAnimation(board): 254. # Randomly reveal the boxes 8 at a time. 255. coveredBoxes = generateRevealedBoxesData(False) 256. boxes = [] 257. for x in range(BOARDWIDTH): 258. for y in range(BOARDHEIGHT): 259. boxes.append( (x, y) ) 260. random.shuffle(boxes) 261. boxGroups = splitIntoGroupsOf(8, boxes) 262. 263. drawBoard(board, coveredBoxes) 264. for boxGroup in boxGroups: 265. revealBoxesAnimation(board, boxGroup) 266. coverBoxesAnimation(board, boxGroup) 267. 268. 269. def gameWonAnimation(board): 270. # flash the background color when the player has won 271. coveredBoxes = generateRevealedBoxesData(True) 272. color1 = LIGHTBGCOLOR 273. color2 = BGCOLOR 274. 275. for i in range(13): 276. color1, color2 = color2, color1 # swap colors 277. DISPLAYSURF.fill(color1) 278. drawBoard(board, coveredBoxes) 279. pygame.display.update() 280. pygame.time.wait(300) 281. 282. 283. def hasWon(revealedBoxes): 284. # Returns True if all the boxes have been revealed, otherwise False 285. for i in revealedBoxes: 286. if False in i: 287. return False # return False if any boxes are covered. 288. return True 289. 290. 291. if __name__ == '__main__': 292. main() |
Vi dụ 2 vòng for lồng nhau
>>> for x in [0, 1, 2, 3, 4]: ... for y in ['a', 'b', 'c']: ... print(x, y) ... 0 a 0 b 0 c 1 a 1 b 1 c 2 a 2 b 2 c 3 a 3 b 3 c 4 a 4 b 4 c >>> |
Import Package
1. # Memory Puzzle 2. # By Al Sweigart al@inventwithpython.com 3. # http://inventwithpython.com/pygame 4. # Released under a "Simplified BSD" license 5. 6. import random, pygame, sys 7. from pygame.locals import * |
- Đứng đầu chương trình là những bình luận về trò chơi là gì, ai đã tạo ra nó và nơi người dùng có thể tìm thêm thông tin. Ngoài ra, còn có một lưu ý rằng mã nguồn có thể sao chép tự do theo giấy phép BSD 'được đơn giản hóa. Giấy phép BSD được đơn giản hóa phù hợp với phần mềm hơn so với giấy phép Creative Common, nhưng về cơ bản chúng có cùng một ý nghĩa: Mọi người có thể tự do sao chép và chia sẻ trò chơi này.
- Chương trình này sử dụng nhiều chức năng trong các mô-đun khác, vì vậy, nó nhập các mô-đun đó trên dòng 6. Dòng 7 cũng là một câu lệnh nhập theo định dạng nhập từ (tên mô-đun), có nghĩa là bạn không phải nhập tên mô-đun vào phía trước của nó Không có chức năng nào trong mô-đun pygame.locals, nhưng có một số biến không đổi trong đó mà chúng tôi muốn sử dụng như MOUSEMOTION, KEYUP hoặc QUIT. Sử dụng kiểu nhập câu lệnh này, chúng ta chỉ phải gõ MOUSEMOTION chứ không phải pygame.locals.MOUSEMOTION.
Magic Numbers
9. FPS = 30 # frames per second, the general speed of the program 10. WINDOWWIDTH = 640 # size of window's width in pixels 11. WINDOWHEIGHT = 480 # size of windows' height in pixels 12. REVEALSPEED = 8 # speed boxes' sliding reveals and covers 13. BOXSIZE = 40 # size of box height & width in pixels 14. GAPSIZE = 10 # size of gap between boxes in pixels |
Các chương trình trò chơi trong cuốn sách này sử dụng rất nhiều biến số không đổi. Bạn có thể không nhận ra lý do tại sao họ rất tiện dụng. Ví dụ: thay vì sử dụng biến BOXSIZE trong mã của chúng tôi, chúng tôi chỉ có thể nhập số nguyên 40 trực tiếp vào mã. Nhưng có hai lý do để sử dụng các biến không đổi.
Đầu tiên, nếu chúng ta muốn thay đổi kích thước của mỗi hộp sau này, chúng ta sẽ phải đi qua toàn bộ chương trình và tìm và thay thế mỗi lần chúng ta gõ 40. Chỉ cần sử dụng hằng số BOXSIZE, chúng ta chỉ phải thay đổi dòng 13 và phần còn lại của chương trình đã được cập nhật. Điều này tốt hơn nhiều, đặc biệt là vì chúng tôi có thể sử dụng giá trị số nguyên 40 cho một thứ khác ngoài kích thước của các hộp màu trắng và việc thay đổi 40 vô tình sẽ gây ra lỗi trong chương trình của chúng tôi.
Thứ hai, nó làm cho mã dễ đọc hơn. Chuyển xuống phần tiếp theo và xem dòng 18. Điều này thiết lập một phép tính cho hằng số XMARGIN, đó là có bao nhiêu pixel ở bên cạnh của toàn bộ bảng. Đó là một biểu hiện phức tạp, nhưng bạn có thể cẩn thận tìm ra ý nghĩa của nó. Dòng 18 trông như thế này:
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2) |
Nhưng nếu dòng 18 không sử dụng các biến không đổi, nó sẽ trông như thế này:
XMARGIN = int((640 – (10 * (40 + 10))) / 2) |
Bây giờ nó trở nên không thể nhớ chính xác những gì lập trình viên dự định. Những con số không giải thích được trong mã nguồn thường được gọi là số ma thuật. Bất cứ khi nào bạn thấy mình nhập số ma thuật, bạn nên xem xét thay thế chúng bằng một biến không đổi thay thế. Đối với trình thông dịch Python, cả hai dòng trước đó đều giống hệt nhau. Nhưng với một lập trình viên người đang đọc mã nguồn và cố gắng hiểu cách thức hoạt động của nó, phiên bản thứ hai của dòng 18 không có ý nghĩa gì cả! Các hằng số thực sự giúp dễ đọc mã nguồn.
Tất nhiên, bạn có thể đi quá xa thay thế số bằng các biến không đổi. Nhìn vào đoạn mã sau:
ZERO = 0 ONE = 1 TWO = 99999999 TWOANDTHREEQUARTERS = 2.75 |
Thiết lập mặt định cho số ô trong khung trò chơi
15. BOARDWIDTH = 10 # number of columns of icons 16. BOARDHEIGHT = 7 # number of rows of icons 17. assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.' 18. XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2) 19. YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2) |
Tuyên bố khẳng định trên dòng 17 đảm bảo rằng chiều rộng và chiều cao của bảng mà chúng tôi đã chọn sẽ dẫn đến số lượng hộp chẵn (vì chúng tôi sẽ có các cặp biểu tượng trong trò chơi này). Có ba phần cho một tuyên bố khẳng định: từ khóa khẳng định, một biểu thức, nếu sai, dẫn đến sự cố chương trình. Phần thứ ba (sau dấu phẩy sau biểu thức) là một chuỗi xuất hiện nếu chương trình gặp sự cố vì xác nhận.
Câu lệnh khẳng định với một biểu thức về cơ bản là nói, Nhà lập trình viên khẳng định rằng biểu thức này phải là True, nếu không thì làm hỏng chương trình. Đây là một cách tốt để thêm kiểm tra độ tỉnh táo vào chương trình của bạn để đảm bảo rằng nếu việc thực thi vượt qua một xác nhận, ít nhất chúng ta có thể biết rằng mã đó đang hoạt động như mong đợi.
Hàm kiểm tra chẵn lẽ
Nếu tích của chiều rộng và chiều cao của bảng được chia cho hai và có phần dư bằng 0 (toán tử% modulus đánh giá phần còn lại là gì) thì số đó là số chẵn. Các số chẵn chia cho hai sẽ luôn có phần còn lại bằng không. Các số lẻ chia cho hai sẽ luôn có phần còn lại là một. Đây là một mẹo hay để nhớ nếu bạn cần mã của mình để biết nếu một số chẵn hay lẻ:
>>> isEven = someNumber % 2 == 0 >>> isOdd = someNumber % 2 != 0 |
Kiểm tra sự cố chương trình
Có sự cố chương trình của bạn là một điều xấu. Nó xảy ra khi chương trình của bạn có một số lỗi trong mã và không thể tiếp tục. Nhưng có một số trường hợp phá vỡ chương trình sớm có thể tránh được các lỗi tồi tệ hơn sau này.
Nếu các giá trị chúng tôi chọn cho BOARDWIDTH và BOARDHEIGHT mà chúng tôi đã chọn trên dòng 15 và 16 dẫn đến một bảng có số lượng hộp lẻ (chẳng hạn như chiều rộng là 3 và chiều cao là 5), thì sẽ luôn có một ô còn lại biểu tượng không có cặp nào phù hợp. Điều này sẽ gây ra lỗi sau này trong chương trình và có thể mất rất nhiều công việc sửa lỗi để tìm ra nguồn gốc thực sự của lỗi là ở phần đầu của chương trình. Trên thực tế, chỉ để giải trí, hãy thử nhận xét xác nhận để nó không chạy, và sau đó đặt các hằng số BOARDWIDTH và BOARDHEIGHT cả hai thành số lẻ. Khi bạn chạy chương trình, nó sẽ ngay lập tức hiển thị một lỗi xảy ra trên một dòng 149 trong memorypuheads.py, đó là trong hàm getRandomizedBoard ()!
Traceback (most recent call last): File "C:\book2svn\src\memorypuzzle.py", line 292, in <module> main() File "C:\book2svn\src\memorypuzzle.py", line 58, in main mainBoard = getRandomizedBoard() File "C:\book2svn\src\memorypuzzle.py", line 149, in getRandomizedBoard columns.append(icons[0]) IndexError: list index out of range |
Chúng tôi có thể dành nhiều thời gian để xem getRandomizedBoard () cố gắng tìm ra lỗi của nó trước khi nhận ra rằng getRandomizedBoard () hoàn toàn ổn: nguồn thực sự của lỗi nằm ở dòng 15 và 16 trong đó chúng tôi đặt các hằng số BOARDWIDTH và BOARDHEIGHT .
Sự khẳng định chắc chắn rằng điều này không bao giờ xảy ra. Nếu mã của chúng tôi sắp sập, chúng tôi muốn nó bị sập ngay khi phát hiện có gì đó không ổn, vì nếu không thì lỗi có thể không rõ ràng cho đến sau này trong chương trình. Sụp đổ sớm!
Bạn muốn thêm các câu khẳng định bất cứ khi nào có một số điều kiện trong chương trình của bạn phải luôn luôn, luôn luôn, luôn luôn là True. Tai nạn thường xuyên! Bạn không cần phải quá nhiệt tình và đặt các tuyên bố khẳng định ở khắp mọi nơi, nhưng thường xuyên gặp sự cố với các xác nhận đi một chặng đường dài trong việc phát hiện nguồn gốc thực sự của một lỗi. Tai nạn sớm và sụp đổ thường xuyên!
Làm cho mã nguồn trông đẹp
21. # R G B 22. GRAY = (100, 100, 100) 23. NAVYBLUE = ( 60, 60, 100) 24. WHITE = (255, 255, 255) 25. RED = (255, 0, 0) 26. GREEN = ( 0, 255, 0) 27. BLUE = ( 0, 0, 255) 28. YELLOW = (255, 255, 0) 29. ORANGE = (255, 128, 0) 30. PURPLE = (255, 0, 255) 31. CYAN = ( 0, 255, 255) 32. 33. BGCOLOR = NAVYBLUE 34. LIGHTBGCOLOR = GRAY 35. BOXCOLOR = WHITE 36. HIGHLIGHTCOLOR = BLUE |
Hãy nhớ rằng màu sắc trong Pygame được biểu thị bằng một bộ ba số nguyên từ 0 đến 255. Ba số nguyên này đại diện cho số lượng màu đỏ, xanh lục và xanh lam trong màu đó là lý do tại sao các bộ dữ liệu này được gọi là giá trị RGB. Lưu ý khoảng cách của các bộ dữ liệu trên các dòng từ 22 đến 31 sao cho các số nguyên R, G và B xếp thành hàng. Trong Python, việc thụt lề (nghĩa là khoảng trắng ở đầu dòng) cần phải chính xác, nhưng khoảng cách trong phần còn lại của dòng không quá nghiêm ngặt. Bằng cách đặt các số nguyên trong tuple ra, chúng ta có thể thấy rõ các giá trị RGB so với nhau như thế nào. (Thông tin thêm về khoảng cách và thụt lề là http://invpy.com/whitespace.)
Thật là một điều tốt đẹp để làm cho mã của bạn dễ đọc hơn theo cách này, nhưng don không làm phiền quá nhiều thời gian để làm điều đó. Code doesn không phải là đẹp để làm việc. Tại một thời điểm nhất định, bạn sẽ chỉ dành nhiều thời gian hơn để nhập không gian hơn mức bạn đã lưu bằng cách có các giá trị tuple có thể đọc được.
Sử dụng biến liên tục thay vì chuỗi
38. DONUT = 'donut' 39. SQUARE = 'square' 40. DIAMOND = 'diamond' 41. LINES = 'lines' 42. OVAL = 'oval' |
Chương trình cũng thiết lập các biến không đổi cho một số chuỗi. Các hằng số này sẽ được sử dụng trong cấu trúc dữ liệu cho bảng, theo dõi khoảng trắng nào trên bảng có biểu tượng nào. Sử dụng một biến không đổi thay vì giá trị chuỗi là một ý tưởng tốt. Nhìn vào đoạn mã sau, xuất phát từ dòng 187:
if shape == DONUT: |
Biến hình dạng sẽ được đặt thành một trong các chuỗi 'donut', 'vuông', 'diamond', 'lines' hoặc 'oval' và sau đó được so sánh với hằng số DONUT. Ví dụ, nếu chúng ta mắc lỗi đánh máy khi viết dòng 187, đại loại như thế này:
if shape == DUNOT: |
Sau đó, Python sẽ gặp sự cố, đưa ra một thông báo lỗi nói rằng không có biến có tên DUNOT. Điều này là tốt Vì chương trình đã bị lỗi trên dòng 187, khi chúng tôi kiểm tra dòng đó, sẽ dễ dàng thấy rằng lỗi là do lỗi đánh máy. Tuy nhiên, nếu chúng ta đang sử dụng các chuỗi thay vì các biến không đổi và tạo ra cùng một lỗi đánh máy, dòng 187 sẽ trông như thế này:
if shape == 'dunot': |
Đây là mã Python hoàn toàn có thể chấp nhận được, vì vậy ban đầu nó đã giành được sự cố khi bạn chạy nó. Tuy nhiên, điều này sẽ dẫn đến các lỗi kỳ lạ sau này trong chương trình của chúng tôi. Bởi vì mã không bị sập ngay lập tức khi xảy ra sự cố, nên có thể khó tìm thấy nó hơn.
Đảm bảo chúng tôi có đủ các biểu tượng
44. ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN) 45. ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL) 46. assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined." |
Để chương trình trò chơi của chúng tôi có thể tạo các biểu tượng của mọi kết hợp màu sắc và hình dạng có thể, chúng tôi cần tạo một bộ dữ liệu chứa tất cả các giá trị này. Ngoài ra còn có một khẳng định khác trên dòng 46 để đảm bảo rằng có đủ kết hợp màu sắc / hình dạng cho kích thước của bảng chúng ta có. Nếu không có, thì chương trình sẽ bị sập trên dòng 46 và chúng ta sẽ biết rằng chúng ta phải thêm nhiều màu sắc và hình dạng hơn, hoặc làm cho chiều rộng và chiều cao của bảng nhỏ hơn. Với 7 màu sắc và 5 hình dạng, chúng ta có thể tạo 35 biểu tượng khác nhau (nghĩa là 7 x 5). Và bởi vì chúng tôi sẽ có một cặp của mỗi biểu tượng, điều đó có nghĩa là chúng tôi có thể có một bảng với tối đa 70 (nghĩa là 35 x 2 hoặc 7 x 5 x 2).
Tuples vs. Lists, Immutable vs. Mutable
Bạn có thể nhận thấy rằng các biến ALLCOLORS và ALLSHAPES là các bộ dữ liệu thay vì danh sách. Khi nào chúng ta muốn sử dụng bộ dữ liệu và khi nào chúng ta muốn sử dụng danh sách? Và điều gì làm nên sự khác biệt giữa họ?
Các bộ và danh sách giống nhau theo mọi cách trừ hai: bộ dữ liệu sử dụng dấu ngoặc đơn thay cho dấu ngoặc vuông và các mục trong bộ dữ liệu không thể được sửa đổi (nhưng các mục trong danh sách có thể được sửa đổi). Chúng ta thường gọi các danh sách có thể thay đổi (có nghĩa là chúng có thể được thay đổi) và bộ dữ liệu không thay đổi (có nghĩa là chúng không thể thay đổi).
Để biết ví dụ về việc cố gắng thay đổi giá trị trong danh sách và bộ dữ liệu, hãy xem đoạn mã sau:
>>> listVal = [1, 1, 2, 3, 5, 8] >>> tupleVal = (1, 1, 2, 3, 5, 8) >>> listVal[4] = 'hello!' >>> listVal [1, 1, 2, 3, 'hello!', 8] >>> tupleVal[4] = 'hello!' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> tupleVal (1, 1, 2, 3, 5, 8) >>> tupleVal[4] 5 |
Lưu ý rằng khi chúng tôi cố gắng thay đổi mục ở chỉ số 2 trong bộ dữ liệu, Python cung cấp cho chúng tôi một thông báo lỗi nói rằng các đối tượng tuple không hỗ trợ gán mục vật phẩm.
Có một lợi ích ngớ ngẩn và một lợi ích quan trọng đối với sự bất biến của Tuple. Lợi ích ngớ ngẩn là mã sử dụng bộ dữ liệu nhanh hơn một chút so với mã sử dụng danh sách. (Python có thể thực hiện một số tối ưu hóa khi biết rằng các giá trị trong một tuple sẽ không bao giờ thay đổi.) Nhưng việc mã của bạn chạy nhanh hơn vài nano giây không phải là điều quan trọng.
Lợi ích quan trọng của việc sử dụng bộ dữ liệu tương tự như lợi ích của việc sử dụng các biến không đổi: đó là dấu hiệu cho thấy giá trị trong bộ dữ liệu sẽ không bao giờ thay đổi, vì vậy bất kỳ ai đọc mã sau này cũng có thể nói, tôi có thể hy vọng rằng bộ dữ liệu này sẽ luôn luôn giống nhau. Nếu không thì lập trình viên đã sử dụng một danh sách. Điều này cũng cho phép một lập trình viên tương lai đọc mã của bạn nói, nếu tôi thấy một giá trị danh sách, tôi biết rằng nó có thể được sửa đổi tại một số điểm trong chương trình này. Nếu không, lập trình viên đã viết mã này sẽ sử dụng một tuple.
Bạn vẫn có thể gán giá trị tuple mới cho một biến:
>>> tupleVal = (1, 2, 3) >>> tupleVal = (1, 2, 3, 4) |
Lý do mã này hoạt động là vì mã isn Thay đổi bộ dữ liệu (1, 2, 3) trên dòng thứ hai. Nó đang gán một tuple hoàn toàn mới (1, 2, 3, 4) cho tupleVal và ghi đè giá trị tuple cũ. Tuy nhiên, bạn không thể sử dụng dấu ngoặc vuông để sửa đổi một mục trong bộ dữ liệu.
Chuỗi cũng là một loại dữ liệu bất biến. Bạn có thể sử dụng dấu ngoặc vuông để đọc một ký tự trong chuỗi, nhưng bạn không thể thay đổi một ký tự trong chuỗi:
>>> strVal = 'Hello' >>> strVal[1] 'e' >>> strVal[1] = 'X' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment |
Một mục Tuples cần một dấu phẩy
Ngoài ra, một chi tiết nhỏ về bộ dữ liệu: nếu bạn cần viết mã về bộ dữ liệu có một giá trị trong đó, thì nó cần phải có dấu phẩy trong đó, chẳng hạn như:
oneValueTuple = (42, ) |
Nếu bạn quên dấu phẩy này (và rất dễ quên), thì Python won Khăn có thể cho biết sự khác biệt giữa cái này và một bộ dấu ngoặc đơn chỉ thay đổi thứ tự các thao tác. Ví dụ, nhìn vào hai dòng mã sau:
variableA = (5 * 6) variableB = (5 * 6, ) |
Giá trị được lưu trữ trong biếnA chỉ là số nguyên 30. Tuy nhiên, biểu thức cho câu lệnh gán biếnBB là giá trị tuple đơn mục (30,). Các giá trị tuple trống không cần dấu phẩy trong chúng, chúng chỉ có thể là một tập các dấu ngoặc đơn: ().
Chuyển đổi giữa danh sách và bộ dữ liệu
Bạn có thể chuyển đổi giữa các giá trị danh sách và tuple giống như bạn có thể chuyển đổi giữa các giá trị chuỗi và số nguyên. Chỉ cần chuyển một giá trị tuple cho hàm list () và nó sẽ trả về một dạng danh sách của giá trị tuple đó. Hoặc, chuyển một giá trị danh sách cho hàm tuple () và nó sẽ trả về một dạng tuple của giá trị danh sách đó. Hãy thử nhập nội dung sau vào vỏ tương tác:
>>> spam = (1, 2, 3, 4) >>> spam = list(spam) >>> spam [1, 2, 3, 4] >>> spam = tuple(spam) >>> spam (1, 2, 3, 4) >>> |
Biến toàn cục
48. def main(): 49. global FPSCLOCK, DISPLAYSURF 50. pygame.init() 51. FPSCLOCK = pygame.time.Clock() 52. DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 53. 54. mousex = 0 # used to store x coordinate of mouse event 55. mousey = 0 # used to store y coordinate of mouse event 56. pygame.display.set_caption('Memory Game') |
Đây là sự khởi đầu của hàm main (), đây là phần chính của mã trò chơi. Các hàm được gọi trong hàm main () sẽ được giải thích sau trong chương này.
Dòng 49 là một tuyên bố toàn cầu. Câu lệnh toàn cầu là từ khóa toàn cầu theo sau là danh sách tên biến được phân cách bằng dấu phẩy. Những tên biến này sau đó được đánh dấu là biến toàn cục. Bên trong hàm main (), các tên đó không dành cho các biến cục bộ có thể có cùng tên với các biến toàn cục. Chúng là các biến toàn cầu. Mọi giá trị được gán cho chúng trong hàm main () sẽ tồn tại bên ngoài hàm main (). Chúng tôi đang đánh dấu các biến FPSCLOCK và DISPLAYSURF là toàn cục vì chúng được sử dụng trong một số chức năng khác trong chương trình. (Thông tin thêm có tại http://invpy.com/scope.)
Có bốn quy tắc đơn giản để xác định xem một biến là cục bộ hay toàn cầu:
1. Nếu có một câu lệnh toàn cục cho một biến ở đầu hàm, thì biến đó là toàn cục.
2. Nếu tên của một biến trong hàm có cùng tên với biến toàn cục và hàm không bao giờ gán cho biến đó một giá trị, thì biến đó là biến toàn cục.
3. Nếu tên của một biến trong hàm có cùng tên với biến toàn cục và hàm đó gán cho biến đó một giá trị, thì biến đó là biến cục bộ.
4. Nếu không có biến toàn cục có cùng tên với biến trong hàm thì biến đó rõ ràng là biến cục bộ.
Bạn thường muốn tránh sử dụng các biến toàn cục bên trong các hàm. Một chức năng được cho là giống như một chương trình nhỏ bên trong chương trình của bạn với các đầu vào cụ thể (các tham số) và đầu ra (giá trị trả về). Nhưng một chức năng đọc và ghi vào các biến toàn cục có thêm đầu vào và đầu ra. Do biến toàn cục có thể đã được sửa đổi ở nhiều nơi trước khi hàm được gọi, nên có thể rất khó để theo dõi một lỗi liên quan đến một giá trị xấu được đặt trong biến toàn cục.
Có một chức năng như một chương trình nhỏ riêng biệt mà không sử dụng các biến toàn cục giúp dễ dàng tìm thấy các lỗi trong mã của bạn, vì các tham số của hàm được biết rõ ràng. Nó cũng làm cho việc thay đổi mã trong một hàm dễ dàng hơn, vì nếu hàm mới hoạt động với cùng tham số và cho cùng một giá trị trả về, nó sẽ tự động hoạt động với phần còn lại của chương trình giống như hàm cũ.
Về cơ bản, sử dụng các biến toàn cục có thể giúp viết chương trình của bạn dễ dàng hơn nhưng chúng thường làm cho việc gỡ lỗi khó hơn.
Trong các trò chơi trong cuốn sách này, các biến toàn cục chủ yếu được sử dụng cho các biến sẽ là hằng số toàn cầu không bao giờ thay đổi, nhưng cần hàm pygame.init () được gọi đầu tiên. Vì điều này xảy ra trong hàm main (), chúng được đặt trong hàm main () và phải là toàn cục để các hàm khác nhìn thấy chúng. Nhưng các biến toàn cục được sử dụng như hằng số và don Thay đổi, vì vậy chúng ít có khả năng gây ra lỗi khó hiểu.
Nếu bạn không hiểu điều này, thì hãy lo lắng. Chỉ cần viết mã của bạn để bạn chuyển các giá trị cho các hàm thay vì các hàm đọc các biến toàn cục làm quy tắc chung.
Cấu trúc dữ liệu và danh sách 2D
58. mainBoard = getRandomizedBoard() 59. revealedBoxes = generateRevealedBoxesData(False) |
Hàm getRandomizedBoard () trả về cấu trúc dữ liệu đại diện cho trạng thái của bảng. Hàm GenerRevealBoxesData () trả về cấu trúc dữ liệu tương ứng với các hộp được bao phủ. Giá trị trả về của các hàm này là danh sách hai chiều (2D) hoặc danh sách danh sách. Danh sách danh sách các danh sách giá trị sẽ là danh sách 3D. Một từ khác cho danh sách hai chiều trở lên là danh sách đa chiều.
Nếu chúng ta có một giá trị danh sách được lưu trữ trong một biến có tên là thư rác, chúng ta có thể truy cập một giá trị trong danh sách đó bằng dấu ngoặc vuông, chẳng hạn như thư rác [2] để truy xuất giá trị thứ ba trong danh sách. Nếu giá trị tại spam [2] tự nó là một danh sách, thì chúng ta có thể sử dụng một bộ dấu ngoặc vuông khác để lấy một giá trị trong danh sách đó. Điều này sẽ trông giống như, ví dụ, thư rác [2] [4], sẽ lấy giá trị thứ năm trong danh sách là giá trị thứ ba trong thư rác. Sử dụng ký hiệu danh sách danh sách này giúp dễ dàng ánh xạ bảng 2D thành giá trị danh sách 2D. Vì biến mainBoard sẽ lưu trữ các biểu tượng trong đó, nếu chúng ta muốn lấy biểu tượng trên bảng ở vị trí (4, 5) thì chúng ta chỉ có thể sử dụng biểu thức mainBoard [4] [5]. Do các biểu tượng được lưu trữ dưới dạng các bộ dữ liệu hai mục với hình dạng và màu sắc, nên cấu trúc dữ liệu hoàn chỉnh là một danh sách các danh sách các bộ dữ liệu hai mục. Phù!
Đây là một ví dụ nhỏ. Nói bảng trông như thế này:
Cấu trúc dữ liệu tương ứng sẽ là:
mainBoard = [[(DONUT, BLUE), (LINES, BLUE), (SQUARE, ORANGE)], [(SQUARE, GREEN), (DONUT, BLUE), (DIAMOND, YELLOW)], [(SQUARE, GREEN), (OVAL, YELLOW), (SQUARE, ORANGE)], [(DIAMOND, YELLOW), (LINES, BLUE), (OVAL, YELLOW)]] |
(Nếu sách của bạn có màu đen và trắng, bạn có thể thấy phiên bản màu của hình ảnh trên tại http://invpy.com/memoryboard.) Bạn sẽ nhận thấy rằng mainBoard [x] [y] sẽ tương ứng với biểu tượng tại tọa độ (x, y) trên bảng.
Trong khi đó, các hộp được tiết lộ Cấu trúc dữ liệu của cấu trúc dữ liệu cũng là một danh sách 2D, ngoại trừ thay vì các bộ dữ liệu hai mục như cấu trúc dữ liệu bảng, nó có các giá trị Boolean: Đúng nếu hộp ở tọa độ x, y đó bị lộ và Sai nếu nó được che đậy. Truyền sai cho hàm GenerRevealBoxesData () đặt tất cả các giá trị Boolean thành Sai. (Chức năng này được giải thích chi tiết sau.)
Hai cấu trúc dữ liệu này được sử dụng để theo dõi trạng thái của bảng trò chơi.
Trò chơi bắt đầu
61. firstSelection = None # stores the (x, y) of the first box clicked. 62. 63. DISPLAYSURF.fill(BGCOLOR) 64. startGameAnimation(mainBoard) |
Dòng 61 thiết lập một biến gọi là FirstSelection với giá trị Không có. (Không có giá trị nào đại diện cho việc thiếu giá trị. Đây là giá trị duy nhất của loại dữ liệu, Không có loại. Thông tin thêm tại http://invpy.com/None) Khi người chơi nhấp vào biểu tượng trên bảng, chương trình cần theo dõi nếu đây là biểu tượng đầu tiên của cặp được nhấp vào hoặc biểu tượng thứ hai. Nếu FirstSelection là Không có, thì nhấp chuột vào biểu tượng đầu tiên và chúng tôi lưu trữ tọa độ XY trong biến FirstSelection dưới dạng một bộ gồm hai số nguyên (một cho giá trị X, còn lại cho Y). Ở lần nhấp thứ hai, giá trị sẽ là tuple này chứ không phải là Không, đó là cách chương trình theo dõi đó là lần nhấp vào biểu tượng thứ hai. Dòng 63 lấp đầy toàn bộ bề mặt với màu nền. Điều này cũng sẽ vẽ lên bất cứ thứ gì đã từng có trên bề mặt, điều này cho chúng ta một bảng xếp hạng sạch sẽ để bắt đầu vẽ đồ họa.
Nếu bạn đã chơi trò chơi Puzzle Memory, bạn sẽ nhận thấy rằng vào đầu trò chơi, tất cả các ô sẽ nhanh chóng được che đậy và phát hiện ra một cách ngẫu nhiên để cho người chơi xem lén các biểu tượng nằm dưới ô nào. Tất cả điều này xảy ra trong hàm startGameAnimation (), được giải thích sau trong chương này.
Nó rất quan trọng để cung cấp cho người chơi cái nhìn lén này (nhưng không đủ lâu để cho phép người chơi dễ dàng ghi nhớ các vị trí biểu tượng), vì nếu không họ sẽ không biết được bất kỳ biểu tượng nào. Nhấp vào các biểu tượng một cách mù quáng cũng thú vị như có một gợi ý nhỏ để tiếp tục.
The Game Loop
66. while True: # main game loop 67. mouseClicked = False 68. 69. DISPLAYSURF.fill(BGCOLOR) # drawing the window 70. drawBoard(mainBoard, revealedBoxes) |
Vòng lặp trò chơi là một vòng lặp vô hạn bắt đầu trên dòng 66, cứ lặp đi lặp lại chừng nào trò chơi đang diễn ra. Hãy nhớ rằng vòng lặp trò chơi xử lý các sự kiện, cập nhật trạng thái trò chơi và vẽ trạng thái trò chơi lên màn hình.
Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
· mainBoard
· revealedBoxes
· firstSelection
· mouseClicked
· mousex
· mousey
Trên mỗi lần lặp của vòng lặp trò chơi trong chương trình Câu đố bộ nhớ, biến mouseClicky lưu trữ giá trị Boolean là True nếu người chơi đã nhấp chuột trong vòng lặp này thông qua vòng lặp trò chơi. (Đây là một phần của việc theo dõi trạng thái trò chơi.)
Trên dòng 69, bề mặt được sơn bằng màu nền để xóa bất cứ thứ gì đã được vẽ trước đó trên nó. Sau đó, chương trình gọi drawBoard () để vẽ trạng thái hiện tại của bảng dựa trên bảng và các hộp được tiết lộ các cấu trúc dữ liệu mà chúng tôi vượt qua. (Những dòng mã này là một phần của bản vẽ và cập nhật màn hình.)
Hãy nhớ rằng các chức năng vẽ của chúng tôi chỉ vẽ trên đối tượng Surface hiển thị trong bộ nhớ. Đối tượng Surface này sẽ không thực sự xuất hiện trên màn hình cho đến khi chúng ta gọi pygame.display.update (), được thực hiện ở cuối vòng lặp trò chơi trên dòng 121.
Vòng xử lý sự kiện
72. for event in pygame.event.get(): # event handling loop 73. if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): 74. pygame.quit() 75. sys.exit() 76. elif event.type == MOUSEMOTION: 77. mousex, mousey = event.pos 78. elif event.type == MOUSEBUTTONUP: 79. mousex, mousey = event.pos 80. mouseClicked = True |
Vòng lặp for trên dòng 72 thực thi mã cho mọi sự kiện đã xảy ra kể từ lần lặp cuối cùng của vòng lặp trò chơi. Vòng lặp này được gọi là vòng xử lý sự kiện (khác với vòng lặp trò chơi, mặc dù vòng xử lý sự kiện nằm trong vòng lặp trò chơi) và lặp lại danh sách các đối tượng pygame.Event được trả về bởi lệnh gọi pygame.event.get () .
Nếu đối tượng sự kiện là sự kiện QUIT hoặc sự kiện KEYUP cho khóa Esc, thì chương trình sẽ chấm dứt. Mặt khác, trong trường hợp xảy ra sự kiện MOUSEMOTION (nghĩa là con trỏ chuột đã di chuyển) hoặc sự kiện MOUSEBUTTONUP (nghĩa là đã nhấn nút chuột trước đó và bây giờ nút này được thả ra), nên lưu trữ vị trí của con trỏ chuột trong các biến mousex và mousey. Nếu đây là sự kiện MOUSEBUTTONUP, mouseClicky cũng nên được đặt thành True.
Khi chúng tôi đã xử lý tất cả các sự kiện, các giá trị được lưu trữ trong mousex, mousey và mouseClicky sẽ cho chúng tôi biết bất kỳ đầu vào nào mà người chơi đã cung cấp cho chúng tôi. Bây giờ chúng ta nên cập nhật trạng thái trò chơi và vẽ kết quả lên màn hình.
Kiểm tra hộp nào Con trỏ chuột đã hết
82. boxx, boxy = getBoxAtPixel(mousex, mousey) 83. if boxx != None and boxy != None: 84. # The mouse is currently over a box. 85. if not revealedBoxes[boxx][boxy]: 86. drawHighlightBox(boxx, boxy) |
Hàm getBoxAtPixel () sẽ trả về một tuple gồm hai số nguyên. Các số nguyên biểu thị tọa độ bảng XY của hộp mà tọa độ chuột kết thúc. Cách getBoxAtPixel () thực hiện việc này được giải thích sau. Tất cả những gì chúng ta phải biết bây giờ là nếu tọa độ mousex và mousey nằm trên một hộp, một tuple của tọa độ bảng XY được trả về bởi hàm và được lưu trữ trong boxx và boxy. Nếu con trỏ chuột không nằm trên bất kỳ hộp nào (ví dụ: nếu nó nằm ở bên cạnh bảng hoặc trong một khoảng trống ở giữa các hộp) thì tuple (Không, Không) được trả về bởi hàm và boxx và boxy sẽ Không có lưu trữ trong chúng.
Chúng tôi chỉ quan tâm đến trường hợp boxx và boxy không có Không có trong đó, vì vậy một số dòng mã tiếp theo nằm trong khối theo câu lệnh if trên dòng 83 kiểm tra trường hợp này. Nếu thực thi đã vào bên trong khối này, chúng tôi biết người dùng có con trỏ chuột trên một hộp (và có thể cũng đã nhấp chuột, tùy thuộc vào giá trị được lưu trữ trong mouseClicky).
Câu lệnh if trên dòng 85 kiểm tra xem hộp có được che đậy hay không bằng cách đọc giá trị được lưu trữ trong yetBoxes [boxx] [boxy]. Nếu nó là Sai, thì chúng ta biết hộp được bảo hiểm. Bất cứ khi nào chuột ở trên một hộp được che kín, chúng tôi muốn vẽ một điểm nổi bật màu xanh xung quanh hộp để thông báo cho người chơi rằng họ có thể nhấp vào nó. Làm nổi bật này không được thực hiện cho các hộp đã được phát hiện. Bản vẽ nổi bật được xử lý bởi hàm drawHighlightBox () của chúng tôi, sẽ được giải thích sau.
87. if not revealedBoxes[boxx][boxy] and mouseClicked: 88. revealBoxesAnimation(mainBoard, [(boxx, boxy)]) 89. revealedBoxes[boxx][boxy] = True # set the box as "revealed" |
Trên dòng 87, chúng tôi kiểm tra xem con trỏ chuột không chỉ nằm trên hộp được che kín mà còn nhấp chuột hay không. Trong trường hợp đó, chúng tôi muốn phát hình ảnh động tiết lộ ra cho hộp đó bằng cách gọi hàm notifyBoxesAnimation () của chúng tôi, như với tất cả các lệnh gọi hàm () khác, được giải thích sau trong chương này). Bạn nên lưu ý rằng việc gọi chức năng này chỉ vẽ hoạt hình của hộp được phát hiện ra. Nó không phải là dòng cho đến dòng 89 khi chúng tôi đặt tiết lộ [boxx] [boxy] = Đúng là cấu trúc dữ liệu theo dõi trạng thái trò chơi được cập nhật.
Nếu bạn nhận xét dòng 89 và sau đó chạy chương trình, bạn sẽ nhận thấy rằng sau khi nhấp vào hộp, hình động hiển thị được phát, nhưng sau đó hộp ngay lập tức xuất hiện lại. Điều này là do yetBoxes [boxx] [boxy] vẫn được đặt thành Sai, vì vậy trong lần lặp tiếp theo của vòng lặp trò chơi, bảng được vẽ với hộp này được che đậy. Không có dòng 89 sẽ gây ra một lỗi khá lạ trong chương trình của chúng tôi.
Xử lý hộp nhấp chuột đầu tiên
90. if firstSelection == None: # the current box was the first box clicked 91. firstSelection = (boxx, boxy) 92. else: # the current box was the second box clicked 93. # Check if there is a match between the two icons. 94. icon1shape, icon1color = getShapeAndColor(mainBoard, firstSelection[0], firstSelection[1]) 95. icon2shape, icon2color = getShapeAndColor(mainBoard, boxx, boxy) |
Trước khi thực hiện bước vào vòng lặp trò chơi, biến FirstSelection được đặt thành Không có. Chương trình của chúng tôi sẽ giải thích điều này có nghĩa là không có hộp nào được nhấp, vì vậy nếu điều kiện dòng 90 là True, điều đó có nghĩa đây là hộp đầu tiên trong số hai hộp có thể được nhấp. Chúng tôi muốn chơi hoạt hình tiết lộ cho hộp và sau đó giữ cho hộp đó không bị che. Chúng tôi cũng đặt biến FirstSelection thành một tuple của tọa độ hộp cho hộp được nhấp.
Nếu đây là hộp thứ hai mà người chơi đã nhấp vào, chúng tôi muốn phát hoạt hình hiển thị cho hộp đó nhưng sau đó kiểm tra xem hai biểu tượng bên dưới các hộp có khớp nhau không. Hàm getShapeAndColor () (sẽ giải thích sau) sẽ lấy ra các giá trị hình dạng và màu sắc của các biểu tượng. (Các giá trị này sẽ là một trong các giá trị trong bộ dữ liệu ALLCOLORS và ALLSHAPES.)
Xử lý một cặp biểu tượng không khớp
97. if icon1shape != icon2shape or icon1color != icon2color: 98. # Icons don't match. Re-cover up both selections. 99. pygame.time.wait(1000) # 1000 milliseconds = 1 sec 100. coverBoxesAnimation(mainBoard, [(firstSelection[0], firstSelection[1]), (boxx, boxy)]) 101. revealedBoxes[firstSelection[0]][firstSelection [1]] = False 102. revealedBoxes[boxx][boxy] = False |
Câu lệnh if trên dòng 97 kiểm tra xem hình dạng hoặc màu sắc của hai biểu tượng không khớp nhau. Nếu đây là trường hợp, thì chúng tôi muốn tạm dừng trò chơi trong 1000 mili giây (tương đương với 1 giây) bằng cách gọi pygame.time.wait (1000) để người chơi có cơ hội thấy rằng hai biểu tượng không ' t khớp. Sau đó, các trang bìa của trò chơi này bao gồm các hoạt hình của trò chơi. Chúng tôi cũng muốn cập nhật trạng thái trò chơi để đánh dấu các ô này là không được tiết lộ (nghĩa là được che đậy).
Xử lý nếu người chơi thắng
103. elif hasWon(revealedBoxes): # check if all pairs found 104. gameWonAnimation(mainBoard) 105. pygame.time.wait(2000) 106. 107. # Reset the board 108. mainBoard = getRandomizedBoard() 109. revealedBoxes = generateRevealedBoxesData(False) 110. 111. # Show the fully unrevealed board for a second. 112. drawBoard(mainBoard, revealedBoxes) 113. pygame.display.update() 114. pygame.time.wait(1000) 115. 116. # Replay the start game animation. 117. startGameAnimation(mainBoard) 118. firstSelection = None # reset firstSelection variable |
Mặt khác, nếu điều kiện 97 dòng là Sai, thì hai biểu tượng phải khớp nhau. Chương trình không thực sự phải làm bất cứ điều gì khác cho các hộp tại thời điểm đó: nó chỉ có thể để cả hai hộp trong trạng thái được tiết lộ. Tuy nhiên, chương trình nên kiểm tra xem đây có phải là cặp biểu tượng cuối cùng trên bảng được khớp hay không. Điều này được thực hiện bên trong hàm hasWon () của chúng ta, trả về True nếu bảng ở trạng thái chiến thắng (nghĩa là tất cả các hộp được tiết lộ).
Nếu đó là trường hợp, chúng tôi muốn chơi trò chơi giành chiến thắng của trò chơi điện tử trên mạng bằng cách gọi trò chơiWonAnimation (), sau đó tạm dừng một chút để người chơi vui mừng trong chiến thắng của họ, sau đó đặt lại cấu trúc dữ liệu trong mainBoard và tiết lộ các hộp để bắt đầu một trò chơi mới.
Dòng 117 chơi lại trò chơi bắt đầu trò chơi hoạt hình trên mạng. Sau đó, việc thực hiện chương trình sẽ chỉ lặp qua vòng lặp trò chơi như bình thường và người chơi có thể tiếp tục chơi cho đến khi họ thoát khỏi chương trình.
Bất kể hai hộp có khớp nhau hay không, sau khi hộp thứ hai được nhấp vào dòng 118 sẽ đặt biến Biến đầu tiên trở về Không để hộp tiếp theo mà người chơi nhấp vào sẽ được hiểu là hộp được nhấp đầu tiên của một cặp có thể khớp biểu tượng.
Vẽ trạng thái trò chơi lên màn hình
120. # Redraw the screen and wait a clock tick. 121. pygame.display.update() 122. FPSCLOCK.tick(FPS) |
Tại thời điểm này, trạng thái trò chơi đã được cập nhật tùy thuộc vào đầu vào của người chơi, và trạng thái trò chơi mới nhất đã được rút ra cho đối tượng Surface hiển thị HIỂN THỊ. Chúng tôi đã đạt đến cuối vòng lặp trò chơi, vì vậy chúng tôi gọi pygame.display.update () để vẽ đối tượng Surface HIỂN THỊ lên màn hình máy tính.
Dòng 9 đặt hằng số FPS thành giá trị số nguyên 30, nghĩa là chúng tôi muốn trò chơi chạy (nhiều nhất) ở 30 khung hình mỗi giây. Nếu chúng ta muốn chương trình chạy nhanh hơn, chúng ta có thể tăng con số này. Nếu chúng ta muốn chương trình chạy chậm hơn, chúng ta có thể giảm số lượng này. Nó thậm chí có thể được đặt thành giá trị float như 0,5, sẽ chạy chương trình với tốc độ nửa khung hình mỗi giây, nghĩa là một khung hình mỗi hai giây.
Để chạy ở tốc độ 30 khung hình mỗi giây, mỗi khung hình phải được vẽ trong 1/30 giây. Điều này có nghĩa là pygame.display.update () và tất cả mã trong vòng lặp trò chơi phải thực thi trong vòng dưới 33,3 mili giây. Bất kỳ máy tính hiện đại nào cũng có thể làm điều này một cách dễ dàng với nhiều thời gian còn lại. Để ngăn chương trình chạy quá nhanh, chúng tôi gọi phương thức tick () của đối tượng pygame.Clock trong FPSCLOCK để nó tạm dừng chương trình trong phần còn lại của 33,3 mili giây.
Vì việc này được thực hiện ở cuối vòng lặp trò chơi, nó đảm bảo rằng mỗi lần lặp của vòng lặp trò chơi sẽ mất (ít nhất) 33,3 mili giây. Nếu vì một lý do nào đó, lệnh gọi pygame.display.update () và mã trong vòng lặp trò chơi mất hơn 33,3 mili giây, thì phương thức tick () sẽ không chờ đợi và ngay lập tức quay lại.
Tôi đã nói rằng các chức năng khác sẽ được giải thích sau trong chương này. Bây giờ chúng tôi đã chuyển qua hàm main () và bạn có ý tưởng về cách chương trình chung hoạt động, hãy để Lùi đi vào chi tiết của tất cả các hàm khác được gọi từ hàm main ().
Tạo “Revealed Boxes” Data Structure
125. def generateRevealedBoxesData(val): 126. revealedBoxes = [] 127. for i in range(BOARDWIDTH): 128. revealedBoxes.append([val] * BOARDHEIGHT) 129. return revealedBoxes |
Hàm GenerRevealBoxesData () cần tạo một danh sách các danh sách các giá trị Boolean. Giá trị Boolean sẽ chỉ là giá trị được truyền cho hàm dưới dạng tham số val. Chúng tôi bắt đầu cấu trúc dữ liệu dưới dạng một danh sách trống trong biến yetBox.
Để tạo cấu trúc dữ liệu có cấu trúc được tiết lộ [x] [y], chúng tôi cần đảm bảo rằng các danh sách bên trong đại diện cho các cột dọc của bảng chứ không phải các hàng ngang. Mặt khác, cấu trúc dữ liệu sẽ có cấu trúc [y] [x] được tiết lộ.
Vòng lặp for sẽ tạo các cột và sau đó nối chúng vào các hộp được tiết lộ. Các cột được tạo bằng cách sử dụng sao chép danh sách, sao cho danh sách cột có nhiều giá trị val như lệnh BOARDHEIGHT.
Tạo cấu trúc dữ liệu bảng
Bước 1 - Nhận tất cả các biểu tượng có thể
132. def getRandomizedBoard(): 133. # Get a list of every possible shape in every possible color. 134. icons = [] 135. for color in ALLCOLORS: 136. for shape in ALLSHAPES: 137. icons.append( (shape, color) ) |
Cấu trúc dữ liệu bảng chỉ là một danh sách các danh sách các bộ dữ liệu, trong đó mỗi bộ có hai giá trị: một cho hình dạng biểu tượng hình chữ nhật và một cho màu biểu tượng. Nhưng việc tạo cấu trúc dữ liệu này hơi phức tạp. Chúng ta cần chắc chắn có chính xác nhiều biểu tượng cho số lượng hộp trên bảng và cũng phải chắc chắn có hai và chỉ hai biểu tượng của mỗi loại.
Bước đầu tiên để làm điều này là tạo một danh sách với mọi sự kết hợp có thể có của hình dạng và màu sắc. Hãy nhớ lại rằng chúng ta có một danh sách từng màu và hình dạng trong ALLCOLORS và ALLSHAPES, do đó, lồng cho các vòng trên dòng 135 và 136 sẽ đi qua mọi hình dạng có thể cho mọi màu có thể. Chúng được thêm vào danh sách trong biến biểu tượng trên dòng 137.
Bước 2 - Xáo trộn và cắt xén danh sách tất cả các biểu tượng
139. random.shuffle(icons) # randomize the order of the icons list 140. numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed 141. icons = icons[:numIconsUsed] * 2 # make two of each 142. random.shuffle(icons) |
Nhưng hãy nhớ rằng, có thể có nhiều kết hợp có thể hơn không gian trên bảng. Chúng ta cần tính toán số lượng khoảng trống trên bảng bằng cách nhân BOARDWIDTH với BOARDHEIGHT. Sau đó, chúng tôi chia số đó cho 2 vì chúng tôi sẽ có các cặp biểu tượng. Trên một bảng có 70 khoảng trắng, chúng tôi chỉ cần 35 biểu tượng khác nhau, vì sẽ có hai biểu tượng. Số này sẽ được lưu trữ trong numIconsUsed.
Dòng 141 sử dụng cắt danh sách để lấy số lượng biểu tượng đầu tiên được sử dụng trong danh sách. (Nếu bạn đã quên cách thức cắt danh sách hoạt động, hãy xem http://invpy.com/slicing.) Danh sách này đã được xáo trộn trên dòng 139, vì vậy nó đã giành chiến thắng trong các trò chơi. Sau đó, danh sách này được sao chép bằng cách sử dụng toán tử * sao cho có hai trong số các biểu tượng. Danh sách nhân đôi mới này sẽ ghi đè lên danh sách cũ trong biến biểu tượng. Vì nửa đầu của danh sách mới này giống hệt với nửa cuối, chúng tôi gọi phương thức shuffle () một lần nữa để trộn ngẫu nhiên thứ tự của các biểu tượng.
Bước 3 - Đặt các biểu tượng trên bảng
144. # Create the board data structure, with randomly placed icons. 145. board = [] 146. for x in range(BOARDWIDTH): 147. column = [] 148. for y in range(BOARDHEIGHT): 149. column.append(icons[0]) 150. del icons[0] # remove the icons as we assign them 151. board.append(column) 152. return board |
Bây giờ chúng ta cần tạo một danh sách các danh sách cấu trúc dữ liệu cho bảng. Chúng ta có thể làm điều này với các vòng lặp lồng nhau giống như hàm createdRevealBoxesData () đã làm. Đối với mỗi cột trên bảng, chúng tôi sẽ tạo một danh sách các biểu tượng được chọn ngẫu nhiên. Khi chúng tôi thêm các biểu tượng vào cột, trên dòng 149, chúng tôi sẽ xóa chúng khỏi mặt trước của danh sách biểu tượng trên dòng 150. Bằng cách này, khi danh sách biểu tượng ngày càng ngắn hơn, các biểu tượng [0] sẽ có một biểu tượng khác để thêm đến các cột.
Để hình dung điều này tốt hơn, hãy nhập mã sau vào vỏ tương tác. Lưu ý cách câu lệnh del thay đổi danh sách myList.
>>> myList = ['cat', 'dog', 'mouse', 'lizard'] >>> del myList[0] >>> myList ['dog', 'mouse', 'lizard'] >>> del myList[0] >>> myList ['mouse', 'lizard'] >>> del myList[0] >>> myList ['lizard'] >>> del myList[0] >>> myList [] >>> |
Bởi vì chúng tôi đang xóa mục ở phía trước danh sách, các mục khác sẽ dịch chuyển về phía trước để mục tiếp theo trong danh sách trở thành mục đầu tiên của Điên mới. Đây là cách tương tự dòng 150 hoạt động.
Tách một Danh sách thành Danh sách của danh sách
155. def splitIntoGroupsOf(groupSize, theList): 156. # splits a list into a list of lists, where the inner lists have at 157. # most groupSize number of items. 158. result = [] 159. for i in range(0, len(theList), groupSize): 160. result.append(theList[i:i + groupSize]) 161. return result |
Hàm splitIntogroupOf () (sẽ được gọi bởi hàm startGameAnimation ()) chia một danh sách thành một danh sách các danh sách, trong đó các danh sách bên trong có số lượng các mục trong nhóm. (Danh sách cuối cùng có thể có ít hơn nếu có ít hơn các mục kích thước nhóm còn lại.)
Cuộc gọi đến phạm vi () trên dòng 159 sử dụng dạng ba tham số của phạm vi (). (Nếu bạn không quen thuộc với hình thức này, hãy xem http://invpy.com/range.) Hãy để sử dụng một ví dụ. Nếu độ dài của danh sách là 20 và tham số groupSize là 8, thì phạm vi (0, len (theList), groupSize) ước tính cho phạm vi (0, 20, 8). Điều này sẽ cung cấp cho biến i các giá trị 0, 8 và 16 cho ba lần lặp của vòng lặp for.
Danh sách cắt trên dòng 160 với Danh sách [i: i + groupSize] tạo danh sách được thêm vào danh sách kết quả. Trên mỗi lần lặp trong đó i là 0, 8 và 16 (và groupSize là 8), biểu thức cắt danh sách này sẽ là List [0: 8], sau đó là List [8:16] trên lần lặp thứ hai và sau đó là List [16: 24] trên lần lặp thứ ba.
Lưu ý rằng mặc dù chỉ số lớn nhất của List sẽ là 19 trong ví dụ của chúng tôi, List [16:24] sẽ không gây ra lỗi IndexError mặc dù 24 lớn hơn 19. Nó sẽ chỉ tạo ra một lát danh sách với các mục còn lại trong danh sách. Cắt danh sách không phá hủy hoặc thay đổi danh sách ban đầu được lưu trữ trong Danh sách. Nó chỉ sao chép một phần của nó để đánh giá một giá trị danh sách mới. Giá trị danh sách mới này là danh sách được thêm vào danh sách trong biến kết quả trên dòng 160. Vì vậy, khi chúng tôi trả về kết quả ở cuối hàm này, chúng tôi sẽ trả về một danh sách các danh sách.
Hệ thống tọa độ khác nhau
164. def leftTopCoordsOfBox(boxx, boxy): 165. # Convert board coordinates to pixel coordinates 166. left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN 167. top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN 168. return (left, top) |
Bạn nên làm quen với các hệ thống tọa độ của Cartesian. (Nếu bạn thích một chương trình giới thiệu về chủ đề này, hãy đọc http://invpy.com/coordins.) Trong hầu hết các trò chơi của chúng tôi, chúng tôi sẽ sử dụng nhiều hệ thống Phối hợp Cartesian. Một hệ thống tọa độ được sử dụng trong trò chơi Câu đố bộ nhớ dành cho tọa độ pixel hoặc màn hình. Nhưng chúng tôi cũng sẽ sử dụng một hệ tọa độ khác cho các hộp. Điều này là do việc sử dụng (3, 2) sẽ dễ dàng hơn khi tham chiếu hộp thứ 4 từ bên trái và thứ 3 từ trên xuống (hãy nhớ rằng các số bắt đầu bằng 0, không phải 1) thay vì sử dụng tọa độ pixel của đỉnh hộp. góc trái, (220, 165). Tuy nhiên, chúng ta cần một cách để dịch giữa hai hệ tọa độ này.
Ở đây, một bức tranh của trò chơi và hai hệ tọa độ khác nhau. Hãy nhớ rằng cửa sổ rộng 640 pixel và cao 480 pixel, vì vậy (639, 479) là góc dưới cùng bên phải (vì góc trên cùng bên trái pixel pixel là (0, 0) chứ không phải (1, 1)).
Hàm leftTopCoordsOfBox () sẽ lấy tọa độ hộp và trả về tọa độ pixel. Vì một hộp chiếm nhiều pixel trên màn hình, chúng tôi sẽ luôn trả lại pixel đơn ở góc trên cùng bên trái của hộp. Giá trị này sẽ được trả về dưới dạng một tuple hai số nguyên. Hàm leftTopCoordsOfBox () thường sẽ được sử dụng khi chúng ta cần tọa độ pixel để vẽ các hộp này.
Chuyển đổi từ tọa độ pixel sang tọa độ hộp
171. def getBoxAtPixel(x, y): 172. for boxx in range(BOARDWIDTH): 173. for boxy in range(BOARDHEIGHT): 174. left, top = leftTopCoordsOfBox(boxx, boxy) 175. boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE) 176. if boxRect.collidepoint(x, y): 177. return (boxx, boxy) 178. return (None, None) |
Chúng ta cũng sẽ cần một chức năng để chuyển đổi từ tọa độ pixel (mà chuột nhấp và các sự kiện di chuyển chuột sử dụng) sang tọa độ hộp (để chúng ta có thể tìm hiểu xem hộp nào xảy ra sự kiện chuột). Các đối tượng Rect có một phương thức collidepoint () mà bạn cũng có thể vượt qua tọa độ X và Y và nó sẽ trả về True nếu tọa độ nằm bên trong (nghĩa là va chạm với) khu vực đối tượng Rect.
Để tìm ra tọa độ chuột nào của hộp, chúng ta sẽ đi qua từng tọa độ của hộp và gọi phương thức collidepoint () trên một đối tượng Rect với các tọa độ đó. Khi collidepoint () trả về True, chúng tôi biết rằng chúng tôi đã tìm thấy hộp được nhấp vào hoặc di chuyển qua và sẽ trả về tọa độ hộp. Nếu không ai trong số họ trả về True, thì hàm getBoxAtPixel () sẽ trả về giá trị (Không có, Không có). Bộ dữ liệu này được trả về thay vì chỉ trả về Không vì người gọi của getBoxAtPixel () đang mong đợi một bộ hai giá trị được trả về.
Vẽ biểu tượng và đường cú pháp
181. def drawIcon(shape, color, boxx, boxy): 182. quarter = int(BOXSIZE * 0.25) # syntactic sugar 183. half = int(BOXSIZE * 0.5) # syntactic sugar 184. 185. left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords |
Hàm drawIcon () sẽ vẽ một biểu tượng (với hình dạng và màu sắc được chỉ định) tại không gian có tọa độ được cho trong các tham số boxx và boxy. Mỗi hình dạng có thể có một tập hợp các hàm vẽ Pygame khác nhau, vì vậy chúng ta phải có một tập hợp lớn các câu lệnh if và elif để phân biệt giữa chúng. (Những tuyên bố này nằm trên dòng 187 đến 198.)
Có thể lấy tọa độ X và Y của cạnh trái và cạnh trên của hộp bằng cách gọi hàm leftTopCoordsOfBox (). Cả chiều rộng và chiều cao của hộp đều được đặt trong hằng số BOXSIZE. Tuy nhiên, nhiều lệnh gọi hàm vẽ hình cũng sử dụng điểm giữa và điểm quý của hộp. Chúng ta có thể tính toán điều này và lưu trữ nó trong các biến quý và một nửa. Chúng ta có thể dễ dàng có mã int (BOXSIZE * 0.25) thay vì quý biến, nhưng theo cách này, mã trở nên dễ đọc hơn vì rõ ràng là quý có nghĩa gì hơn là int (BOXSIZE * 0.25).
Các biến như vậy là một ví dụ về đường cú pháp. Đường cú pháp là khi chúng ta thêm mã có thể được viết theo cách khác (có thể với ít mã và biến thực tế hơn), nhưng làm cho mã nguồn dễ đọc hơn. Biến không đổi là một dạng của cú pháp đường. Tính toán trước một giá trị và lưu trữ nó trong một biến là một loại đường cú pháp khác. (Ví dụ: trong hàm getRandomizedBoard (), chúng ta có thể dễ dàng biến mã trên các dòng 140 và dòng 141 thành một dòng mã. Nhưng dễ đọc hơn là hai dòng riêng biệt.) Chúng ta không cần phải có thêm một phần tư và một nửa biến, nhưng có chúng làm cho mã dễ đọc hơn. Mã dễ đọc rất dễ gỡ lỗi và nâng cấp trong tương lai.
186. # Draw the shapes 187. if shape == DONUT: 188. pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5) 189. pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5) 190. elif shape == SQUARE: 191. pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half)) 192. elif shape == DIAMOND: 193. pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half))) 194. elif shape == LINES: 195. for i in range(0, BOXSIZE, 4): 196. pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top)) 197. pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i)) 198. elif shape == OVAL: 199. pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half)) |
Mỗi chức năng của bánh rán, hình vuông, hình thoi, đường thẳng và hình bầu dục đòi hỏi các lệnh gọi hàm nguyên thủy khác nhau để thực hiện.
Đường cú pháp với việc lấy một bảng không gian bảng từ Biểu tượng hình dạng và màu sắc
202. def getShapeAndColor(board, boxx, boxy): 203. # shape value for x, y spot is stored in board[x][y][0] 204. # color value for x, y spot is stored in board[x][y][1] 205. return board[boxx][boxy][0], board[boxx][boxy][1] |
Hàm getShapeAndColor () chỉ có một dòng. Bạn có thể tự hỏi tại sao chúng ta lại muốn một hàm thay vì chỉ gõ một dòng mã đó bất cứ khi nào chúng ta cần. Điều này được thực hiện với cùng lý do chúng tôi sử dụng các biến không đổi: nó cải thiện khả năng đọc của mã.
Nó dễ dàng tìm ra một mã như hình dạng, màu sắc = getShapeAndColor () làm gì. Nhưng nếu bạn đã xem một mã như hình dạng, color = board [boxx] [boxy] [0], board [boxx] [boxy] [1], sẽ khó khăn hơn một chút để tìm ra.
Vẽ the Box Cover
208. def drawBoxCovers(board, boxes, coverage): 209. # Draws boxes being covered/revealed. "boxes" is a list 210. # of two-item lists, which have the x & y spot of the box. 211. for box in boxes: 212. left, top = leftTopCoordsOfBox(box[0], box[1]) 213. pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE)) 214. shape, color = getShapeAndColor(board, box[0], box[1]) 215. drawIcon(shape, color, box[0], box[1]) 216. if coverage > 0: # only draw the cover if there is an coverage 217. pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE)) 218. pygame.display.update() 219. FPSCLOCK.tick(FPS) |
Hàm drawBoxCovers () có ba tham số: cấu trúc dữ liệu bảng, danh sách các bộ dữ liệu (X, Y) cho mỗi hộp nên có nắp được vẽ và sau đó là mức độ bao phủ để vẽ cho các hộp.
Vì chúng tôi muốn sử dụng cùng một mã bản vẽ cho mỗi hộp trong tham số hộp, chúng tôi sẽ sử dụng một vòng lặp for trên dòng 211 để chúng tôi thực thi cùng một mã trên mỗi hộp trong danh sách hộp. Bên trong vòng lặp này, mã phải thực hiện ba điều: vẽ màu nền (để tô lên bất cứ thứ gì đã có trước đó), vẽ biểu tượng, sau đó vẽ phần lớn hộp màu trắng lên biểu tượng cần thiết. Hàm leftTopCoordsOfBox () sẽ trả về tọa độ pixel của góc trên cùng bên trái của hộp. Câu lệnh if trên dòng 216 đảm bảo rằng nếu số trong phạm vi bảo hiểm xảy ra nhỏ hơn 0, chúng tôi đã giành chiến thắng Gọi hàm pygame.draw.rect ().
Khi tham số bảo hiểm là 0, không có phạm vi bảo hiểm nào cả. Khi phạm vi bảo hiểm được đặt thành 20, có một hộp trắng rộng 20 pixel bao phủ biểu tượng. Kích thước lớn nhất mà chúng tôi sẽ muốn phạm vi được đặt là số trong BOXSIZE, trong đó toàn bộ biểu tượng được bao phủ hoàn toàn.
drawBoxCovers () sẽ được gọi từ một vòng lặp riêng biệt hơn vòng lặp trò chơi. Do đó, nó cần có các lệnh gọi riêng tới pygame.display.update () và FPSCLOCK.tick (FPS) để hiển thị hình ảnh động. (Điều này không có nghĩa là trong khi bên trong vòng lặp này, không có mã nào được chạy để xử lý bất kỳ sự kiện nào được tạo ra. Điều đó tốt, vì ảnh bìa và tiết lộ hoạt hình chỉ mất một giây để chơi.)
Xử lý hoạt hình tiết lộ và bao phủ – Handling the Revealing and Covering Animation
222. def revealBoxesAnimation(board, boxesToReveal): 223. # Do the "box reveal" animation. 224. for coverage in range(BOXSIZE, (-REVEALSPEED) - 1, - REVEALSPEED): 225. drawBoxCovers(board, boxesToReveal, coverage) 226. 227. 228. def coverBoxesAnimation(board, boxesToCover): 229. # Do the "box cover" animation. 230. for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED): 231. drawBoxCovers(board, boxesToCover, coverage) |
Hãy nhớ rằng một hình ảnh động chỉ đơn giản là hiển thị các hình ảnh khác nhau trong những khoảnh khắc ngắn ngủi và cùng nhau chúng làm cho nó có vẻ như mọi thứ đang di chuyển trên màn hình. Tiết lộBoxesAnimation () và coverBoxesAnimation () chỉ cần vẽ một biểu tượng với mức độ bao phủ khác nhau của hộp màu trắng. Chúng ta có thể viết một hàm duy nhất gọi là drawBoxCovers () có thể thực hiện điều này và sau đó có chức năng hoạt hình của chúng ta gọi drawBoxCovers () cho mỗi khung hình của hình ảnh động. Như chúng ta đã thấy trong phần trước, drawBoxCovers () thực hiện cuộc gọi đến pygame.display.update () và FPSCLOCK.tick (FPS).
Để thực hiện việc này, chúng tôi sẽ thiết lập một vòng lặp for để giảm (trong trường hợp tiết lộBoxesAnimation ()) hoặc tăng (trong trường hợp số coverBoxesAnimation ()) cho tham số bảo hiểm. Số lượng mà biến bảo hiểm sẽ giảm / tăng là số trong hằng số REVEALSPEED. Trên dòng 12, chúng tôi đặt hằng số này thành 8, nghĩa là trên mỗi lệnh gọi tới drawBoxCovers (), hộp trắng sẽ giảm / tăng 8 pixel trên mỗi lần lặp. Nếu chúng ta tăng số này, thì nhiều pixel sẽ được vẽ trên mỗi cuộc gọi, nghĩa là hộp trắng sẽ giảm / tăng kích thước nhanh hơn. Nếu chúng ta đặt nó thành 1, thì hộp trắng sẽ chỉ xuất hiện giảm hoặc tăng 1 pixel trên mỗi lần lặp, làm cho toàn bộ hoạt động tiết lộ hoặc bìa hoạt động mất nhiều thời gian hơn.
Hãy nghĩ về nó như leo cầu thang. Nếu trên mỗi bước bạn đi, bạn leo lên một cầu thang, thì sẽ mất một khoảng thời gian bình thường để leo lên toàn bộ cầu thang. Nhưng nếu bạn leo hai cầu thang cùng một lúc trên mỗi bước (và các bước chỉ mất nhiều thời gian như trước), bạn có thể leo lên toàn bộ cầu thang nhanh gấp đôi. Nếu bạn có thể leo cầu thang 8 bậc một lúc, thì bạn sẽ leo lên toàn bộ cầu thang nhanh gấp 8 lần.
Drawing the Entire Board
234. def drawBoard(board, revealed): 235. # Draws all of the boxes in their covered or revealed state. 236. for boxx in range(BOARDWIDTH): 237. for boxy in range(BOARDHEIGHT): 238. left, top = leftTopCoordsOfBox(boxx, boxy) 239. if not revealed[boxx][boxy]: 240. # Draw a covered box. 241. pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE)) 242. else: 243. # Draw the (revealed) icon. 244. shape, color = getShapeAndColor(board, boxx, boxy) 245. drawIcon(shape, color, boxx, boxy) |
Hàm drawBoard () thực hiện cuộc gọi đến drawIcon () cho mỗi hộp trên bảng. Các vòng lặp lồng nhau trên các dòng 236 và 237 sẽ lặp qua mọi tọa độ X và Y có thể cho các hộp và sẽ vẽ biểu tượng tại vị trí đó hoặc thay vào đó là một hình vuông màu trắng (để biểu thị một hộp được che kín).
Vẽ nổi bật
248. def drawHighlightBox(boxx, boxy): 249. left, top = leftTopCoordsOfBox(boxx, boxy) 250. pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5, BOXSIZE + 10, BOXSIZE + 10), 4) |
Để giúp người chơi nhận ra rằng họ có thể nhấp vào một hộp được bảo hiểm để tiết lộ nó, chúng tôi sẽ làm một đường viền màu xanh xuất hiện xung quanh một hộp để làm nổi bật nó. Phác thảo này được vẽ bằng lệnh gọi pygame.draw.rect () để tạo hình chữ nhật có chiều rộng 4 pixel.
The “Start Game” Animation
253. def startGameAnimation(board): 254. # Randomly reveal the boxes 8 at a time. 255. coveredBoxes = generateRevealedBoxesData(False) 256. boxes = [] 257. for x in range(BOARDWIDTH): 258. for y in range(BOARDHEIGHT): 259. boxes.append( (x, y) ) 260. random.shuffle(boxes) 261. boxGroups = splitIntoGroupsOf(8, boxes) |
Hoạt hình phát vào đầu trò chơi cung cấp cho người chơi một gợi ý nhanh về vị trí của tất cả các biểu tượng. Để thực hiện hoạt hình này, chúng tôi phải tiết lộ và che đậy các nhóm hộp này đến nhóm khác. Để làm điều này, đầu tiên chúng tôi sẽ tạo một danh sách mọi không gian có thể có trên bảng. Các vòng lặp lồng nhau trên các dòng 257 và 258 sẽ thêm các bộ dữ liệu (X, Y) vào danh sách trong biến hộp.
Chúng tôi sẽ tiết lộ và che đậy 8 hộp đầu tiên trong danh sách này, sau đó là 8 hộp tiếp theo, sau đó là 8 hộp tiếp theo, v.v. Tuy nhiên, vì thứ tự của các bộ dữ liệu (X, Y) trong các hộp sẽ giống nhau mỗi lần, nên thứ tự các hộp tương tự sẽ được hiển thị. (Hãy thử nhận xét dòng 260 và sau đó chạy để lập trình một vài lần để thấy hiệu ứng này.)
Để thay đổi các hộp mỗi khi trò chơi bắt đầu, chúng ta sẽ gọi hàm Random.shuffle () để xáo trộn ngẫu nhiên thứ tự các bộ dữ liệu trong danh sách hộp. Sau đó, khi chúng tôi tiết lộ và che đậy 8 hộp đầu tiên trong danh sách này (và mỗi nhóm 8 hộp sau đó), đó sẽ là nhóm ngẫu nhiên gồm 8 hộp.
Để có được danh sách 8 hộp, chúng tôi gọi hàm splitIntogroupOf () của chúng tôi, chuyển 8 và danh sách trong các hộp. Danh sách các danh sách mà hàm trả về sẽ được lưu trữ trong một biến có tên boxgroup.
Revealing and Covering the Groups of Boxes
263. drawBoard(board, coveredBoxes) 264. for boxGroup in boxGroups: 265. revealBoxesAnimation(board, boxGroup) 266. coverBoxesAnimation(board, boxGroup) |
Đầu tiên, chúng tôi vẽ bảng. Vì mọi giá trị trong coverBox được đặt thành Sai, lệnh gọi này đến drawBoard () sẽ kết thúc bản vẽ chỉ được che kín các hộp màu trắng. Các hàm notifyBoxesAnimation () và coverBoxesAnimation () sẽ vẽ lên khoảng trắng của các hộp trắng này.
Vòng lặp for sẽ đi qua từng danh sách bên trong trong danh sách nhóm. Chúng tôi chuyển những thứ này để tiết lộBoxesAnimation (), sẽ thực hiện hoạt ảnh của các hộp màu trắng được kéo đi để lộ biểu tượng bên dưới. Sau đó, lệnh gọi coverBoxesAnimation () sẽ làm động các hộp trắng mở rộng để che các biểu tượng. Sau đó, vòng lặp for đi đến lần lặp tiếp theo để tạo hiệu ứng cho bộ 8 hộp tiếp theo.
The “Game Won” Animation
269. def gameWonAnimation(board): 270. # flash the background color when the player has won 271. coveredBoxes = generateRevealedBoxesData(True) 272. color1 = LIGHTBGCOLOR 273. color2 = BGCOLOR 274. 275. for i in range(13): 276. color1, color2 = color2, color1 # swap colors 277. DISPLAYSURF.fill(color1) 278. drawBoard(board, coveredBoxes) 279. pygame.display.update() 280. pygame.time.wait(300) |
Khi người chơi đã phát hiện ra tất cả các ô bằng cách khớp tất cả các cặp trên bảng, chúng tôi muốn chúc mừng chúng bằng cách nhấp nháy màu nền. Vòng lặp for sẽ vẽ màu trong biến color1 cho màu nền và sau đó vẽ bảng lên trên nó. Tuy nhiên, trên mỗi lần lặp của vòng lặp for, các giá trị trong color1 và color2 sẽ được hoán đổi với nhau trên dòng 276. Bằng cách này, chương trình sẽ xen kẽ giữa việc vẽ hai màu nền khác nhau.
Hãy nhớ rằng hàm này cần gọi pygame.display.update () để thực sự làm cho bề mặt HIỂN THỊ xuất hiện trên màn hình.
Telling if the Player Has Won
283. def hasWon(revealedBoxes): 284. # Returns True if all the boxes have been revealed, otherwise False 285. for i in revealedBoxes: 286. if False in i: 287. return False # return False if any boxes are covered. 288. return True |
Người chơi đã giành chiến thắng trong trò chơi khi tất cả các cặp biểu tượng đã được khớp. Do cấu trúc dữ liệu được tiết lộ, các cấu trúc dữ liệu được đặt thành True khi các biểu tượng được khớp, chúng ta chỉ cần lặp qua mọi khoảng trống trong các hộp được tiết lộ để tìm giá trị Sai. Nếu thậm chí một giá trị Sai nằm trong tiết lộ, thì chúng ta biết vẫn còn các biểu tượng chưa từng có trên bảng.
Lưu ý rằng vì yetBox là danh sách các danh sách, vòng lặp for trên dòng 285 sẽ đặt danh sách bên trong làm giá trị của i. Nhưng chúng ta có thể sử dụng toán tử in để tìm kiếm giá trị Sai trong toàn bộ danh sách bên trong. Bằng cách này, chúng tôi không cần phải viết một dòng mã bổ sung và có hai vòng lặp lồng nhau như thế này:
for x in revealedBoxes: for y in revealedBoxes[x]: if False == revealedBoxes[x][y]: return False |
Why Bother Having a main() Function?
291. if __name__ == '__main__': 292. main() |
Có vẻ như vô nghĩa khi có hàm main (), vì bạn chỉ có thể đặt mã đó trong phạm vi toàn cầu ở cuối chương trình và mã sẽ chạy chính xác. Tuy nhiên, có hai lý do chính đáng để đặt chúng bên trong hàm main ().
Đầu tiên, điều này cho phép bạn có các biến cục bộ trong khi nếu không thì các biến cục bộ trong hàm main () sẽ phải trở thành các biến toàn cục. Giới hạn số lượng biến toàn cục là một cách tốt để giữ cho mã đơn giản và dễ gỡ lỗi hơn. (Xem phần Tại sao các biến toàn cầu là phần Evil Evil trong chương này.)
Thứ hai, điều này cũng cho phép bạn nhập chương trình để bạn có thể gọi và kiểm tra các chức năng riêng lẻ. Nếu tệp memorypuheads.py nằm trong thư mục C: \ Python32, thì bạn có thể nhập tệp đó từ trình vỏ tương tác. Nhập thông tin sau để kiểm tra các hàm splitInto ERICOf () và getBoxAtPixel () để đảm bảo chúng trả về các giá trị trả về chính xác:
>>> import memorypuzzle >>> memorypuzzle.splitIntoGroupsOf(3, [0,1,2,3,4,5,6,7,8,9]) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] >>> memorypuzzle.getBoxAtPixel(0, 0) (None, None) >>> memorypuzzle.getBoxAtPixel(150, 150) (1, 1) |
Khi một mô-đun được nhập, tất cả các mã trong nó được chạy. Nếu chúng tôi không có hàm main () và có mã trong phạm vi toàn cầu, thì trò chơi sẽ tự động bắt đầu ngay khi chúng tôi nhập nó, điều này thực sự sẽ cho phép chúng tôi gọi các hàm riêng lẻ trong đó.
Đó là lý do tại sao mã nằm trong một hàm riêng biệt mà chúng ta đã đặt tên là main (). Sau đó, chúng tôi kiểm tra biến Python tích hợp __name__ để xem chúng ta có nên gọi hàm main () hay không. Biến này được trình thông dịch Python tự động đặt thành chuỗi '__main__' nếu chương trình đang được chạy và 'bộ nhớ' nếu nó đang được nhập. Đây là lý do tại sao hàm main () không chạy khi chúng ta thực thi câu lệnh nhập bộ nhớ trong vỏ tương tác.
Đây là một kỹ thuật tiện dụng để có thể nhập chương trình bạn đang làm việc từ trình vỏ tương tác và đảm bảo các hàm riêng lẻ đang trả về các giá trị chính xác bằng cách kiểm tra chúng mỗi lần một cuộc gọi.
Why Bother With Readability?
Rất nhiều gợi ý trong chương này đã nói về cách viết chương trình mà máy tính có thể chạy nhiều như cách viết chương trình mà lập trình viên có thể đọc. Bạn có thể không hiểu tại sao điều này lại quan trọng. Rốt cuộc, miễn là mã hoạt động, ai quan tâm nếu nó khó hay dễ cho lập trình viên con người đọc?
Tuy nhiên, điều quan trọng để nhận ra về phần mềm là nó hiếm khi bị bỏ lại một mình. Khi bạn đang tạo các trò chơi của riêng mình, bạn sẽ hiếm khi được thực hiện với chương trình. Bạn sẽ luôn có được những ý tưởng mới cho các tính năng trò chơi mà bạn muốn thêm hoặc tìm các lỗi mới với chương trình. Bởi vì điều này, điều quan trọng là chương trình của bạn có thể đọc được để bạn có thể xem mã và hiểu nó. Và hiểu mã là bước đầu tiên để thay đổi nó để thêm mã hoặc sửa lỗi.
Ví dụ, đây là một phiên bản bị xáo trộn của chương trình Câu đố bộ nhớ được thực hiện hoàn toàn không thể đọc được. Nếu bạn nhập nó vào (hoặc tải xuống từ http://invpy.com/memorypuheads_obfuscated.py) và chạy nó, bạn sẽ thấy nó chạy chính xác như mã ở đầu chương này. Nhưng nếu có một lỗi với mã này, bạn sẽ không thể đọc được mã và hiểu được những gì đang xảy ra, ít sửa lỗi hơn.
Máy tính không mã tâm trí không thể đọc được như thế này. Nó rất giống với nó.
import random, pygame, sys from pygame.locals import * def hhh(): global a, b pygame.init() a = pygame.time.Clock() b = pygame.display.set_mode((640, 480)) j = 0 k = 0 pygame.display.set_caption('Memory Game') i = c() hh = d(False) h = None b.fill((60, 60, 100)) g(i) while True: e = False b.fill((60, 60, 100)) f(i, hh) for eee in pygame.event.get(): if eee.type == QUIT or (eee.type == KEYUP and eee.key == K_ESCAPE): pygame.quit() sys.exit() elif eee.type == MOUSEMOTION: j, k = eee.pos elif eee.type == MOUSEBUTTONUP: j, k = eee.pos e = True bb, ee = m(j, k) if bb != None and ee != None: if not hh[bb][ee]: n(bb, ee) if not hh[bb][ee] and e: o(i, [(bb, ee)]) hh[bb][ee] = True if h == None: h = (bb, ee) else: q, fff = s(i, h[0], h[1]) r, ggg = s(i, bb, ee) if q != r or fff != ggg: pygame.time.wait(1000) p(i, [(h[0], h[1]), (bb, ee)]) hh[h[0]][h[1]] = False hh[bb][ee] = False elif ii(hh): jj(i) pygame.time.wait(2000) i = c() hh = d(False) f(i, hh) pygame.display.update() pygame.time.wait(1000) g(i) h = None pygame.display.update() a.tick(30) def d(ccc): hh = [] for i in range(10): hh.append([ccc] * 7) return hh def c(): rr = [] for tt in ((255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 128, 0), (255, 0, 255), (0, 255, 255)): for ss in ('a', 'b', 'c', 'd', 'e'): rr.append( (ss, tt) ) random.shuffle(rr) rr = rr[:35] * 2 random.shuffle(rr) bbb = [] for x in range(10): v = [] for y in range(7): v.append(rr[0]) del rr[0] bbb.append(v) return bbb def t(vv, uu): ww = [] for i in range(0, len(uu), vv): ww.append(uu[i:i + vv]) return ww def aa(bb, ee): return (bb * 50 + 70, ee * 50 + 65) def m(x, y): for bb in range(10): for ee in range(7): oo, ddd = aa(bb, ee) aaa = pygame.Rect(oo, ddd, 40, 40) if aaa.collidepoint(x, y): return (bb, ee) return (None, None) def w(ss, tt, bb, ee): oo, ddd = aa(bb, ee) if ss == 'a': pygame.draw.circle(b, tt, (oo + 20, ddd + 20), 15) pygame.draw.circle(b, (60, 60, 100), (oo + 20, ddd + 20), 5) elif ss == 'b': pygame.draw.rect(b, tt, (oo + 10, ddd + 10, 20, 20)) elif ss == 'c': pygame.draw.polygon(b, tt, ((oo + 20, ddd), (oo + 40 - 1, ddd + 20), (oo + 20, ddd + 40 - 1), (oo, ddd + 20))) elif ss == 'd': for i in range(0, 40, 4): pygame.draw.line(b, tt, (oo, ddd + i), (oo + i, ddd)) pygame.draw.line(b, tt, (oo + i, ddd + 39), (oo + 39, ddd + i)) elif ss == 'e': pygame.draw.ellipse(b, tt, (oo, ddd + 10, 40, 20)) def s(bbb, bb, ee): return bbb[bb][ee][0], bbb[bb][ee][1] def dd(bbb, boxes, gg): for box in boxes: oo, ddd = aa(box[0], box[1]) pygame.draw.rect(b, (60, 60, 100), (oo, ddd, 40, 40)) ss, tt = s(bbb, box[0], box[1]) w(ss, tt, box[0], box[1]) if gg > 0: pygame.draw.rect(b, (255, 255, 255), (oo, ddd, gg, 40)) pygame.display.update() a.tick(30) def o(bbb, cc): for gg in range(40, (-8) - 1, -8): dd(bbb, cc, gg) def p(bbb, ff): for gg in range(0, 48, 8): dd(bbb, ff, gg) def f(bbb, pp): for bb in range(10): for ee in range(7): oo, ddd = aa(bb, ee) if not pp[bb][ee]: pygame.draw.rect(b, (255, 255, 255), (oo, ddd, 40, 40)) else: ss, tt = s(bbb, bb, ee) w(ss, tt, bb, ee) def n(bb, ee): oo, ddd = aa(bb, ee) pygame.draw.rect(b, (0, 0, 255), (oo - 5, ddd - 5, 50, 50), 4) def g(bbb): mm = d(False) boxes = [] for x in range(10): for y in range(7): boxes.append( (x, y) ) random.shuffle(boxes) kk = t(8, boxes) f(bbb, mm) for nn in kk: o(bbb, nn) p(bbb, nn) def jj(bbb): mm = d(True) tt1 = (100, 100, 100) tt2 = (60, 60, 100) for i in range(13): tt1, tt2 = tt2, tt1 b.fill(tt1) f(bbb, mm) pygame.display.update() pygame.time.wait(300) def ii(hh): for i in hh: if False in i: return False return True if __name__ == '__main__': hhh() |
Đừng bao giờ viết mã như thế này. Nếu bạn lập trình như thế này trong khi đối diện với gương trong phòng tắm với đèn tắt, hồn ma Ada Lovelace sẽ ra khỏi gương và ném bạn vào hàm của khung dệt Jacquard.
Tóm tắt và Đề xuất hack
Chương này bao gồm toàn bộ lời giải thích về cách chương trình Memory Puzzle hoạt động. Đọc lại chương và mã nguồn một lần nữa để hiểu rõ hơn về nó. Nhiều chương trình trò chơi khác trong cuốn sách này sử dụng các khái niệm lập trình giống nhau (như lồng cho các vòng lặp, đường cú pháp và các hệ tọa độ khác nhau trong cùng một chương trình), vì vậy chúng đã thắng giải thích để giữ cuốn sách này ngắn lại.
Một ý tưởng để thử hiểu cách thức hoạt động của mã là cố tình phá vỡ nó bằng cách nhận xét các dòng ngẫu nhiên. Làm điều này với một số dòng có thể sẽ gây ra lỗi cú pháp sẽ ngăn không cho tập lệnh chạy. Nhưng bình luận ra các dòng khác sẽ dẫn đến các lỗi kỳ lạ và các hiệu ứng tuyệt vời khác. Hãy thử làm điều này và sau đó tìm hiểu tại sao một chương trình có lỗi.
Đây cũng là bước đầu tiên để có thể thêm các mánh gian lận hoặc hack bí mật của riêng bạn vào chương trình. Bằng cách phá vỡ chương trình khỏi những gì nó thường làm, bạn có thể học cách thay đổi nó để làm một cái gì đó hiệu quả gọn gàng (như bí mật cho bạn gợi ý về cách giải câu đố). Hãy thử nghiệm. Bạn luôn có thể lưu một bản sao của mã nguồn không thay đổi trong một tệp khác nếu bạn muốn chơi lại trò chơi thông thường.
Giải thích source code Game
#sử dụng thư viện #hàm chính của chương trình #revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa #tọa độ chuột lúc clicked #tô màu cho nền #gọi hàm game start """ #vòng lặp trò chơi #nếu co tọa độ hộp #kiểm tra xem nếu hộp chưa tiết lộ thì ta vẽ HightlightBox xung quanh hộp #đặt trạng thái tiết lộ tại hộp này là True #nếu đây là lần chọn hộp 1 thì gán tọa độ hộp cho firstSelection #nếu đây là lần cọn hợp thứ 2 tức là đã có chọn hộp 1 rồi #trường hợp 2 hộp giống nhau thì giữ nguyên không cần xử lý gì thêm #nếu 2 hộp khác nhau thì xử lý #kiểm tra xem tất cả các hộp đã được tiết lộ chưa #hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val #hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình) #lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon # tạo danh sách 2 chiều chứa các icon, màu sắc # xóa icons[0] đi #trả lại danh sách 2 chiều chứa icon, colors #tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize #chuyển hộp icon thành tọa độ pixcel #lấy tọa độ hộp từ tọa độ pixcel #vẽ icon với shape,corlor tại vị trí boxx, boxy def getShapeAndColor(board, boxx, boxy):
#lấy icon, màu sắc của hộp tại box(x,y) #vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage #cập nhật lại màn hình
#vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover #hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa def drawHighlightBox(boxx, boxy): #hàm bắt đầu chơi game #xáo trộn thứ tự các hộp #tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước #vẽ khung trò chơi với board và coveredBoxes #từ boxGroups tạo hiệu ứng tiết lộ các box #trường hợp click đúng 2 hộp giống nhau #hàm kiểm tra xem có tất cả các hộp đã được tiết lộ chưa #chương trình bắt đầu chạy từ đây và gọi hàm main |
Cám ơn bạn đã xem bài viết.

