Advertisement
asweigart

Tetromino Python program with standard colors

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