March 11, 2019

kll

kjl

1. # Tetromino (a Tetris clone)

  2. # By Al Sweigart [email protected]

  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()