March 11, 2019
kll
kjl
1. # Tetromino (a Tetris clone)
2. # By Al Sweigart al@inventwithpython.com
3. # http://inventwithpython.com/pygame
4. # Creative Commons BY-NC-SA 3.0 US
5.
6. import random, time, pygame, sys
7. from pygame.locals import *
8.
9. FPS = 25
10. WINDOWWIDTH = 640
11. WINDOWHEIGHT = 480
12. BOXSIZE = 20
13. BOARDWIDTH = 10
14. BOARDHEIGHT = 20
15. BLANK = '.'
16.
17. MOVESIDEWAYSFREQ = 0.15
18. MOVEDOWNFREQ = 0.1
19.
20. XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2)
21. TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5
22.
23. # R G B
24. WHITE = (255, 255, 255)
25. GRAY = (185, 185, 185)
26. BLACK = ( 0, 0, 0)
27. RED = (155, 0, 0)
28. LIGHTRED = (175, 20, 20)
29. GREEN = ( 0, 155, 0)
30. LIGHTGREEN = ( 20, 175, 20)
31. BLUE = ( 0, 0, 155)
32. LIGHTBLUE = ( 20, 20, 175)
33. YELLOW = (155, 155, 0)
34. LIGHTYELLOW = (175, 175, 20)
35.
36. BORDERCOLOR = BLUE
37. BGCOLOR = BLACK
38. TEXTCOLOR = WHITE
39. TEXTSHADOWCOLOR = GRAY
40. COLORS = ( BLUE, GREEN, RED, YELLOW)
41. LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW)
42. assert len(COLORS) == len(LIGHTCOLORS) # each color must have light color
43.
44. TEMPLATEWIDTH = 5
45. TEMPLATEHEIGHT = 5
46.
47. S_SHAPE_TEMPLATE = [['.....',
48. '.....',
49. '..OO.',
50. '.OO..',
51. '.....'],
52. ['.....',
53. '..O..',
54. '..OO.',
55. '...O.',
56. '.....']]
57.
58. Z_SHAPE_TEMPLATE = [['.....',
59. '.....',
60. '.OO..',
61. '..OO.',
62. '.....'],
63. ['.....',
64. '..O..',
65. '.OO..',
66. '.O...',
67. '.....']]
68.
69. I_SHAPE_TEMPLATE = [['..O..',
70. '..O..',
71. '..O..',
72. '..O..',
73. '.....'],
74. ['.....',
75. '.....',
76. 'OOOO.',
77. '.....',
78. '.....']]
79.
80. O_SHAPE_TEMPLATE = [['.....',
81. '.....',
82. '.OO..',
83. '.OO..',
84. '.....']]
85.
86. J_SHAPE_TEMPLATE = [['.....',
87. '.O...',
88. '.OOO.',
89. '.....',
90. '.....'],
91. ['.....',
92. '..OO.',
93. '..O..',
94. '..O..',
95. '.....'],
96. ['.....',
97. '.....',
98. '.OOO.',
99. '...O.',
100. '.....'],
101. ['.....',
102. '..O..',
103. '..O..',
104. '.OO..',
105. '.....']]
106.
107. L_SHAPE_TEMPLATE = [['.....',
108. '...O.',
109. '.OOO.',
110. '.....',
111. '.....'],
112. ['.....',
113. '..O..',
114. '..O..',
115. '..OO.',
116. '.....'],
117. ['.....',
118. '.....',
119. '.OOO.',
120. '.O...',
121. '.....'],
122. ['.....',
123. '.OO..',
124. '..O..',
125. '..O..',
126. '.....']]
127.
128. T_SHAPE_TEMPLATE = [['.....',
129. '..O..',
130. '.OOO.',
131. '.....',
132. '.....'],
133. ['.....',
134. '..O..',
135. '..OO.',
136. '..O..',
137. '.....'],
138. ['.....',
139. '.....',
140. '.OOO.',
141. '..O..',
142. '.....'],
143. ['.....',
144. '..O..',
145. '.OO..',
146. '..O..',
147. '.....']]
148.
149. SHAPES = {'S': S_SHAPE_TEMPLATE,
150. 'Z': Z_SHAPE_TEMPLATE,
151. 'J': J_SHAPE_TEMPLATE,
152. 'L': L_SHAPE_TEMPLATE,
153. 'I': I_SHAPE_TEMPLATE,
154. 'O': O_SHAPE_TEMPLATE,
155. 'T': T_SHAPE_TEMPLATE}
156.
157.
158. def main():
159. global FPSCLOCK, DISPLAYSURF, BASICFONT, BIGFONT
160. pygame.init()
161. FPSCLOCK = pygame.time.Clock()
162. DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
163. BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
164. BIGFONT = pygame.font.Font('freesansbold.ttf', 100)
165. pygame.display.set_caption('Tetromino')
166.
167. showTextScreen('Tetromino')
168. while True: # game loop
169. if random.randint(0, 1) == 0:
170. pygame.mixer.music.load('tetrisb.mid')
171. else:
172. pygame.mixer.music.load('tetrisc.mid')
173. pygame.mixer.music.play(-1, 0.0)
174. runGame()
175. pygame.mixer.music.stop()
176. showTextScreen('Game Over')
177.
178.
179. def runGame():
180. # setup variables for the start of the game
181. board = getBlankBoard()
182. lastMoveDownTime = time.time()
183. lastMoveSidewaysTime = time.time()
184. lastFallTime = time.time()
185. movingDown = False # note: there is no movingUp variable
186. movingLeft = False
187. movingRight = False
188. score = 0
189. level, fallFreq = calculateLevelAndFallFreq(score)
190.
191. fallingPiece = getNewPiece()
192. nextPiece = getNewPiece()
193.
194. while True: # main game loop
195. if fallingPiece == None:
196. # No falling piece in play, so start a new piece at the top
197. fallingPiece = nextPiece
198. nextPiece = getNewPiece()
199. lastFallTime = time.time() # reset lastFallTime
200.
201. if not isValidPosition(board, fallingPiece):
202. return # can't fit a new piece on the board, so game over
203.
204. checkForQuit()
205. for event in pygame.event.get(): # event handling loop
206. if event.type == KEYUP:
207. if (event.key == K_p):
208. # Pausing the game
209. DISPLAYSURF.fill(BGCOLOR)
210. pygame.mixer.music.stop()
211. showTextScreen('Paused') # pause until a key press
212. pygame.mixer.music.play(-1, 0.0)
213. lastFallTime = time.time()
214. lastMoveDownTime = time.time()
215. lastMoveSidewaysTime = time.time()
216. elif (event.key == K_LEFT or event.key == K_a):
217. movingLeft = False
218. elif (event.key == K_RIGHT or event.key == K_d):
219. movingRight = False
220. elif (event.key == K_DOWN or event.key == K_s):
221. movingDown = False
222.
223. elif event.type == KEYDOWN:
224. # moving the block sideways
225. if (event.key == K_LEFT or event.key == K_a) and isValidPosition(board, fallingPiece, adjX=-1):
226. fallingPiece['x'] -= 1
227. movingLeft = True
228. movingRight = False
229. lastMoveSidewaysTime = time.time()
230.
231. elif (event.key == K_RIGHT or event.key == K_d) and isValidPosition(board, fallingPiece, adjX=1):
232. fallingPiece['x'] += 1
233. movingRight = True
234. movingLeft = False
235. lastMoveSidewaysTime = time.time()
236.
237. # rotating the block (if there is room to rotate)
238. elif (event.key == K_UP or event.key == K_w):
239. fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(SHAPES[fallingPiece['shape']])
240. if not isValidPosition(board, fallingPiece):
241. fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(SHAPES[fallingPiece['shape']])
242. elif (event.key == K_q): # rotate the other direction
243. fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(SHAPES[fallingPiece['shape']])
244. if not isValidPosition(board, fallingPiece):
245. fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(SHAPES[fallingPiece['shape']])
246.
247. # making the block fall faster with the down key
248. elif (event.key == K_DOWN or event.key == K_s):
249. movingDown = True
250. if isValidPosition(board, fallingPiece, adjY=1):
251. fallingPiece['y'] += 1
252. lastMoveDownTime = time.time()
253.
254. # move the current block all the way down
255. elif event.key == K_SPACE:
256. movingDown = False
257. movingLeft = False
258. movingRight = False
259. for i in range(1, BOARDHEIGHT):
260. if not isValidPosition(board, fallingPiece, adjY=i):
261. break
262. fallingPiece['y'] += i - 1
263.
264. # handle moving the block because of user input
265. if (movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ:
266. if movingLeft and isValidPosition(board, fallingPiece, adjX=-1):
267. fallingPiece['x'] -= 1
268. elif movingRight and isValidPosition(board, fallingPiece, adjX=1):
269. fallingPiece['x'] += 1
270. lastMoveSidewaysTime = time.time()
271.
272. if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1):
273. fallingPiece['y'] += 1
274. lastMoveDownTime = time.time()
275.
276. # let the piece fall if it is time to fall
277. if time.time() - lastFallTime > fallFreq:
278. # see if the piece has landed
279. if not isValidPosition(board, fallingPiece, adjY=1):
280. # falling piece has landed, set it on the board
281. addToBoard(board, fallingPiece)
282. score += removeCompleteLines(board)
283. level, fallFreq = calculateLevelAndFallFreq(score)
284. fallingPiece = None
285. else:
286. # piece did not land, just move the block down
287. fallingPiece['y'] += 1
288. lastFallTime = time.time()
289.
290. # drawing everything on the screen
291. DISPLAYSURF.fill(BGCOLOR)
292. drawBoard(board)
293. drawStatus(score, level)
294. drawNextPiece(nextPiece)
295. if fallingPiece != None:
296. drawPiece(fallingPiece)
297.
298. pygame.display.update()
299. FPSCLOCK.tick(FPS)
300.
301.
302. def makeTextObjs(text, font, color):
303. surf = font.render(text, True, color)
304. return surf, surf.get_rect()
305.
306.
307. def terminate():
308. pygame.quit()
309. sys.exit()
310.
311.
312. def checkForKeyPress():
313. # Go through event queue looking for a KEYUP event.
314. # Grab KEYDOWN events to remove them from the event queue.
315. checkForQuit()
316.
317. for event in pygame.event.get([KEYDOWN, KEYUP]):
318. if event.type == KEYDOWN:
319. continue
320. return event.key
321. return None
322.
323.
324. def showTextScreen(text):
325. # This function displays large text in the
326. # center of the screen until a key is pressed.
327. # Draw the text drop shadow
328. titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTSHADOWCOLOR)
329. titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
330. DISPLAYSURF.blit(titleSurf, titleRect)
331.
332. # Draw the text
333. titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTCOLOR)
334. titleRect.center = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2) - 3)
335. DISPLAYSURF.blit(titleSurf, titleRect)
336.
337. # Draw the additional "Press a key to play." text.
338. pressKeySurf, pressKeyRect = makeTextObjs('Press a key to play.', BASICFONT, TEXTCOLOR)
339. pressKeyRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 100)
340. DISPLAYSURF.blit(pressKeySurf, pressKeyRect)
341.
342. while checkForKeyPress() == None:
343. pygame.display.update()
344. FPSCLOCK.tick()
345.
346.
347. def checkForQuit():
348. for event in pygame.event.get(QUIT): # get all the QUIT events
349. terminate() # terminate if any QUIT events are present
350. for event in pygame.event.get(KEYUP): # get all the KEYUP events
351. if event.key == K_ESCAPE:
352. terminate() # terminate if the KEYUP event was for the Esc key
353. pygame.event.post(event) # put the other KEYUP event objects back
354.
355.
356. def calculateLevelAndFallFreq(score):
357. # Based on the score, return the level the player is on and
358. # how many seconds pass until a falling piece falls one space.
359. level = int(score / 10) + 1
360. fallFreq = 0.27 - (level * 0.02)
361. return level, fallFreq
362.
363. def getNewPiece():
364. # return a random new piece in a random rotation and color
365. shape = random.choice(list(SHAPES.keys()))
366. newPiece = {'shape': shape,
367. 'rotation': random.randint(0, len(SHAPES[shape]) - 1),
368. 'x': int(BOARDWIDTH / 2) - int(TEMPLATEWIDTH / 2),
369. 'y': -2, # start it above the board (i.e. less than 0)
370. 'color': random.randint(0, len(COLORS)-1)}
371. return newPiece
372.
373.
374. def addToBoard(board, piece):
375. # fill in the board based on piece's location, shape, and rotation
376. for x in range(TEMPLATEWIDTH):
377. for y in range(TEMPLATEHEIGHT):
378. if SHAPES[piece['shape']][piece['rotation']][y][x] != BLANK:
379. board[x + piece['x']][y + piece['y']] = piece['color']
380.
381.
382. def getBlankBoard():
383. # create and return a new blank board data structure
384. board = []
385. for i in range(BOARDWIDTH):
386. board.append([BLANK] * BOARDHEIGHT)
387. return board
388.
389.
390. def isOnBoard(x, y):
391. return x >= 0 and x < BOARDWIDTH and y < BOARDHEIGHT
392.
393.
394. def isValidPosition(board, piece, adjX=0, adjY=0):
395. # Return True if the piece is within the board and not colliding
396. for x in range(TEMPLATEWIDTH):
397. for y in range(TEMPLATEHEIGHT):
398. isAboveBoard = y + piece['y'] + adjY < 0
399. if isAboveBoard or SHAPES[piece['shape']][piece['rotation']][y][x] == BLANK:
400. continue
401. if not isOnBoard(x + piece['x'] + adjX, y + piece['y'] + adjY):
402. return False
403. if board[x + piece['x'] + adjX][y + piece['y'] + adjY] != BLANK:
404. return False
405. return True
406.
407. def isCompleteLine(board, y):
408. # Return True if the line filled with boxes with no gaps.
409. for x in range(BOARDWIDTH):
410. if board[x][y] == BLANK:
411. return False
412. return True
413.
414.
415. def removeCompleteLines(board):
416. # Remove any completed lines on the board, move everything above them down, and return the number of complete lines.
417. numLinesRemoved = 0
418. y = BOARDHEIGHT - 1 # start y at the bottom of the board
419. while y >= 0:
420. if isCompleteLine(board, y):
421. # Remove the line and pull boxes down by one line.
422. for pullDownY in range(y, 0, -1):
423. for x in range(BOARDWIDTH):
424. board[x][pullDownY] = board[x][pullDownY-1]
425. # Set very top line to blank.
426. for x in range(BOARDWIDTH):
427. board[x][0] = BLANK
428. numLinesRemoved += 1
429. # Note on the next iteration of the loop, y is the same.
430. # This is so that if the line that was pulled down is also
431. # complete, it will be removed.
432. else:
433. y -= 1 # move on to check next row up
434. return numLinesRemoved
435.
436.
437. def convertToPixelCoords(boxx, boxy):
438. # Convert the given xy coordinates of the board to xy
439. # coordinates of the location on the screen.
440. return (XMARGIN + (boxx * BOXSIZE)), (TOPMARGIN + (boxy * BOXSIZE))
441.
442.
443. def drawBox(boxx, boxy, color, pixelx=None, pixely=None):
444. # draw a single box (each tetromino piece has four boxes)
445. # at xy coordinates on the board. Or, if pixelx & pixely
446. # are specified, draw to the pixel coordinates stored in
447. # pixelx & pixely (this is used for the "Next" piece).
448. if color == BLANK:
449. return
450. if pixelx == None and pixely == None:
451. pixelx, pixely = convertToPixelCoords(boxx, boxy)
452. pygame.draw.rect(DISPLAYSURF, COLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 1, BOXSIZE - 1))
453. pygame.draw.rect(DISPLAYSURF, LIGHTCOLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 4, BOXSIZE - 4))
454.
455.
456. def drawBoard(board):
457. # draw the border around the board
458. pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (XMARGIN - 3, TOPMARGIN - 7, (BOARDWIDTH * BOXSIZE) + 8, (BOARDHEIGHT * BOXSIZE) + 8), 5)
459.
460. # fill the background of the board
461. pygame.draw.rect(DISPLAYSURF, BGCOLOR, (XMARGIN, TOPMARGIN, BOXSIZE * BOARDWIDTH, BOXSIZE * BOARDHEIGHT))
462. # draw the individual boxes on the board
463. for x in range(BOARDWIDTH):
464. for y in range(BOARDHEIGHT):
465. drawBox(x, y, board[x][y])
466.
467.
468. def drawStatus(score, level):
469. # draw the score text
470. scoreSurf = BASICFONT.render('Score: %s' % score, True, TEXTCOLOR)
471. scoreRect = scoreSurf.get_rect()
472. scoreRect.topleft = (WINDOWWIDTH - 150, 20)
473. DISPLAYSURF.blit(scoreSurf, scoreRect)
474.
475. # draw the level text
476. levelSurf = BASICFONT.render('Level: %s' % level, True, TEXTCOLOR)
477. levelRect = levelSurf.get_rect()
478. levelRect.topleft = (WINDOWWIDTH - 150, 50)
479. DISPLAYSURF.blit(levelSurf, levelRect)
480.
481.
482. def drawPiece(piece, pixelx=None, pixely=None):
483. shapeToDraw = SHAPES[piece['shape']][piece['rotation']]
484. if pixelx == None and pixely == None:
485. # if pixelx & pixely hasn't been specified, use the location stored in the piece data structure
486. pixelx, pixely = convertToPixelCoords(piece['x'], piece['y'])
487.
488. # draw each of the blocks that make up the piece
489. for x in range(TEMPLATEWIDTH):
490. for y in range(TEMPLATEHEIGHT):
491. if shapeToDraw[y][x] != BLANK:
492. drawBox(None, None, piece['color'], pixelx + (x * BOXSIZE), pixely + (y * BOXSIZE))
493.
494.
495. def drawNextPiece(piece):
496. # draw the "next" text
497. nextSurf = BASICFONT.render('Next:', True, TEXTCOLOR)
498. nextRect = nextSurf.get_rect()
499. nextRect.topleft = (WINDOWWIDTH - 120, 80)
500. DISPLAYSURF.blit(nextSurf, nextRect)
501. # draw the "next" piece
502. drawPiece(piece, pixelx=WINDOWWIDTH-120, pixely=100)
503.
504.
505. if __name__ == '__main__':
506. main()
March 11, 2019, 11:52
0 views
0 reactions
0 reposts