wip
This commit is contained in:
parent
f55c2fe32b
commit
de1001dd57
30 changed files with 1451 additions and 3 deletions
521
tetris.py
Normal file
521
tetris.py
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
# Tetromino (a Tetris clone)
|
||||
# By Al Sweigart al@inventwithpython.com
|
||||
# http://inventwithpython.com/pygame
|
||||
# Released under a "Simplified BSD" license
|
||||
|
||||
import random
|
||||
import time
|
||||
import pygame
|
||||
import sys
|
||||
from pygame.locals import *
|
||||
|
||||
FPS = 25
|
||||
WINDOWWIDTH = 640
|
||||
WINDOWZE = 20
|
||||
BOARHEIGHT = 480
|
||||
BOXSIDWIDTH = 10
|
||||
BOARDHEIGHT = 20
|
||||
BLANK = '.'
|
||||
|
||||
MOVESIDEWAYSFREQ = 0.15
|
||||
MOVEDOWNFREQ = 0.1
|
||||
|
||||
XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2)
|
||||
TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5
|
||||
|
||||
# R G B
|
||||
WHITE = (255, 255, 255)
|
||||
GRAY = (185, 185, 185)
|
||||
BLACK = (0, 0, 0)
|
||||
RED = (155, 0, 0)
|
||||
LIGHTRED = (175, 20, 20)
|
||||
GREEN = (0, 155, 0)
|
||||
LIGHTGREEN = (20, 175, 20)
|
||||
BLUE = (0, 0, 155)
|
||||
LIGHTBLUE = (20, 20, 175)
|
||||
YELLOW = (155, 155, 0)
|
||||
LIGHTYELLOW = (175, 175, 20)
|
||||
|
||||
BORDERCOLOR = BLUE
|
||||
BGCOLOR = BLACK
|
||||
TEXTCOLOR = WHITE
|
||||
TEXTSHADOWCOLOR = GRAY
|
||||
COLORS = (BLUE, GREEN, RED, YELLOW)
|
||||
LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW)
|
||||
assert len(COLORS) == len(LIGHTCOLORS) # each color must have light color
|
||||
|
||||
TEMPLATEWIDTH = 5
|
||||
TEMPLATEHEIGHT = 5
|
||||
|
||||
S_SHAPE_TEMPLATE = [['.....',
|
||||
'.....',
|
||||
'..OO.',
|
||||
'.OO..',
|
||||
'.....'],
|
||||
['.....',
|
||||
'..O..',
|
||||
'..OO.',
|
||||
'...O.',
|
||||
'.....']]
|
||||
|
||||
Z_SHAPE_TEMPLATE = [['.....',
|
||||
'.....',
|
||||
'.OO..',
|
||||
'..OO.',
|
||||
'.....'],
|
||||
['.....',
|
||||
'..O..',
|
||||
'.OO..',
|
||||
'.O...',
|
||||
'.....']]
|
||||
|
||||
I_SHAPE_TEMPLATE = [['..O..',
|
||||
'..O..',
|
||||
'..O..',
|
||||
'..O..',
|
||||
'.....'],
|
||||
['.....',
|
||||
'.....',
|
||||
'OOOO.',
|
||||
'.....',
|
||||
'.....']]
|
||||
|
||||
O_SHAPE_TEMPLATE = [['.....',
|
||||
'.....',
|
||||
'.OO..',
|
||||
'.OO..',
|
||||
'.....']]
|
||||
|
||||
J_SHAPE_TEMPLATE = [['.....',
|
||||
'.O...',
|
||||
'.OOO.',
|
||||
'.....',
|
||||
'.....'],
|
||||
['.....',
|
||||
'..OO.',
|
||||
'..O..',
|
||||
'..O..',
|
||||
'.....'],
|
||||
['.....',
|
||||
'.....',
|
||||
'.OOO.',
|
||||
'...O.',
|
||||
'.....'],
|
||||
['.....',
|
||||
'..O..',
|
||||
'..O..',
|
||||
'.OO..',
|
||||
'.....']]
|
||||
|
||||
L_SHAPE_TEMPLATE = [['.....',
|
||||
'...O.',
|
||||
'.OOO.',
|
||||
'.....',
|
||||
'.....'],
|
||||
['.....',
|
||||
'..O..',
|
||||
'..O..',
|
||||
'..OO.',
|
||||
'.....'],
|
||||
['.....',
|
||||
'.....',
|
||||
'.OOO.',
|
||||
'.O...',
|
||||
'.....'],
|
||||
['.....',
|
||||
'.OO..',
|
||||
'..O..',
|
||||
'..O..',
|
||||
'.....']]
|
||||
|
||||
T_SHAPE_TEMPLATE = [['.....',
|
||||
'..O..',
|
||||
'.OOO.',
|
||||
'.....',
|
||||
'.....'],
|
||||
['.....',
|
||||
'..O..',
|
||||
'..OO.',
|
||||
'..O..',
|
||||
'.....'],
|
||||
['.....',
|
||||
'.....',
|
||||
'.OOO.',
|
||||
'..O..',
|
||||
'.....'],
|
||||
['.....',
|
||||
'..O..',
|
||||
'.OO..',
|
||||
'..O..',
|
||||
'.....']]
|
||||
|
||||
PIECES = {'S': S_SHAPE_TEMPLATE,
|
||||
'Z': Z_SHAPE_TEMPLATE,
|
||||
'J': J_SHAPE_TEMPLATE,
|
||||
'L': L_SHAPE_TEMPLATE,
|
||||
'I': I_SHAPE_TEMPLATE,
|
||||
'O': O_SHAPE_TEMPLATE,
|
||||
'T': T_SHAPE_TEMPLATE}
|
||||
|
||||
|
||||
def main():
|
||||
global FPSCLOCK, DISPLAYSURF, BASICFONT, BIGFONT
|
||||
pygame.init()
|
||||
FPSCLOCK = pygame.time.Clock()
|
||||
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
|
||||
BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
|
||||
BIGFONT = pygame.font.Font('freesansbold.ttf', 100)
|
||||
pygame.display.set_caption('Tetromino')
|
||||
|
||||
showTextScreen('Tetromino')
|
||||
while True: # game loop
|
||||
# if random.randint(0, 1) == 0:
|
||||
# pygame.mixer.music.load('tetrisb.mid')
|
||||
# else:
|
||||
# pygame.mixer.music.load('tetrisc.mid')
|
||||
#pygame.mixer.music.play(-1, 0.0)
|
||||
runGame()
|
||||
# pygame.mixer.music.stop()
|
||||
showTextScreen('Game Over')
|
||||
|
||||
|
||||
def runGame():
|
||||
# setup variables for the start of the game
|
||||
board = getBlankBoard()
|
||||
lastMoveDownTime = time.time()
|
||||
lastMoveSidewaysTime = time.time()
|
||||
lastFallTime = time.time()
|
||||
movingDown = False # note: there is no movingUp variable
|
||||
movingLeft = False
|
||||
movingRight = False
|
||||
score = 0
|
||||
level, fallFreq = calculateLevelAndFallFreq(score)
|
||||
|
||||
fallingPiece = getNewPiece()
|
||||
nextPiece = getNewPiece()
|
||||
|
||||
while True: # game loop
|
||||
if fallingPiece == None:
|
||||
# No falling piece in play, so start a new piece at the top
|
||||
fallingPiece = nextPiece
|
||||
nextPiece = getNewPiece()
|
||||
lastFallTime = time.time() # reset lastFallTime
|
||||
|
||||
if not isValidPosition(board, fallingPiece):
|
||||
return # can't fit a new piece on the board, so game over
|
||||
|
||||
checkForQuit()
|
||||
for event in pygame.event.get(): # event handling loop
|
||||
if event.type == KEYUP:
|
||||
if (event.key == K_p):
|
||||
# Pausing the game
|
||||
DISPLAYSURF.fill(BGCOLOR)
|
||||
pygame.mixer.music.stop()
|
||||
showTextScreen('Paused') # pause until a key press
|
||||
pygame.mixer.music.play(-1, 0.0)
|
||||
lastFallTime = time.time()
|
||||
lastMoveDownTime = time.time()
|
||||
lastMoveSidewaysTime = time.time()
|
||||
elif (event.key == K_LEFT or event.key == K_a):
|
||||
movingLeft = False
|
||||
elif (event.key == K_RIGHT or event.key == K_d):
|
||||
movingRight = False
|
||||
elif (event.key == K_DOWN or event.key == K_s):
|
||||
movingDown = False
|
||||
|
||||
elif event.type == KEYDOWN:
|
||||
# moving the piece sideways
|
||||
if (event.key == K_LEFT or event.key == K_a) and isValidPosition(board, fallingPiece, adjX=-1):
|
||||
fallingPiece['x'] -= 1
|
||||
movingLeft = True
|
||||
movingRight = False
|
||||
lastMoveSidewaysTime = time.time()
|
||||
|
||||
elif (event.key == K_RIGHT or event.key == K_d) and isValidPosition(board, fallingPiece, adjX=1):
|
||||
fallingPiece['x'] += 1
|
||||
movingRight = True
|
||||
movingLeft = False
|
||||
lastMoveSidewaysTime = time.time()
|
||||
|
||||
# rotating the piece (if there is room to rotate)
|
||||
elif (event.key == K_UP or event.key == K_w):
|
||||
fallingPiece['rotation'] = (
|
||||
fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']])
|
||||
if not isValidPosition(board, fallingPiece):
|
||||
fallingPiece['rotation'] = (
|
||||
fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']])
|
||||
elif (event.key == K_q): # rotate the other direction
|
||||
fallingPiece['rotation'] = (
|
||||
fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']])
|
||||
if not isValidPosition(board, fallingPiece):
|
||||
fallingPiece['rotation'] = (
|
||||
fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']])
|
||||
|
||||
# making the piece fall faster with the down key
|
||||
elif (event.key == K_DOWN or event.key == K_s):
|
||||
movingDown = True
|
||||
if isValidPosition(board, fallingPiece, adjY=1):
|
||||
fallingPiece['y'] += 1
|
||||
lastMoveDownTime = time.time()
|
||||
|
||||
# move the current piece all the way down
|
||||
elif event.key == K_SPACE:
|
||||
movingDown = False
|
||||
movingLeft = False
|
||||
movingRight = False
|
||||
for i in range(1, BOARDHEIGHT):
|
||||
if not isValidPosition(board, fallingPiece, adjY=i):
|
||||
break
|
||||
fallingPiece['y'] += i - 1
|
||||
|
||||
# handle moving the piece because of user input
|
||||
if (movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ:
|
||||
if movingLeft and isValidPosition(board, fallingPiece, adjX=-1):
|
||||
fallingPiece['x'] -= 1
|
||||
elif movingRight and isValidPosition(board, fallingPiece, adjX=1):
|
||||
fallingPiece['x'] += 1
|
||||
lastMoveSidewaysTime = time.time()
|
||||
|
||||
if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1):
|
||||
fallingPiece['y'] += 1
|
||||
lastMoveDownTime = time.time()
|
||||
|
||||
# let the piece fall if it is time to fall
|
||||
if time.time() - lastFallTime > fallFreq:
|
||||
# see if the piece has landed
|
||||
if not isValidPosition(board, fallingPiece, adjY=1):
|
||||
# falling piece has landed, set it on the board
|
||||
addToBoard(board, fallingPiece)
|
||||
score += removeCompleteLines(board)
|
||||
level, fallFreq = calculateLevelAndFallFreq(score)
|
||||
fallingPiece = None
|
||||
else:
|
||||
# piece did not land, just move the piece down
|
||||
fallingPiece['y'] += 1
|
||||
lastFallTime = time.time()
|
||||
|
||||
# drawing everything on the screen
|
||||
DISPLAYSURF.fill(BGCOLOR)
|
||||
drawBoard(board)
|
||||
drawStatus(score, level)
|
||||
drawNextPiece(nextPiece) # Here
|
||||
if fallingPiece != None:
|
||||
drawPiece(fallingPiece)
|
||||
|
||||
pygame.display.update()
|
||||
FPSCLOCK.tick(FPS)
|
||||
|
||||
|
||||
def makeTextObjs(text, font, color):
|
||||
surf = font.render(text, True, color)
|
||||
return surf, surf.get_rect()
|
||||
|
||||
|
||||
def terminate():
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
||||
|
||||
def checkForKeyPress():
|
||||
# Go through event queue looking for a KEYUP event.
|
||||
# Grab KEYDOWN events to remove them from the event queue.
|
||||
checkForQuit()
|
||||
|
||||
for event in pygame.event.get([KEYDOWN, KEYUP]):
|
||||
if event.type == KEYDOWN:
|
||||
continue
|
||||
return event.key
|
||||
return None
|
||||
|
||||
|
||||
def showTextScreen(text):
|
||||
# This function displays large text in the
|
||||
# center of the screen until a key is pressed.
|
||||
# Draw the text drop shadow
|
||||
titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTSHADOWCOLOR)
|
||||
titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
|
||||
DISPLAYSURF.blit(titleSurf, titleRect)
|
||||
|
||||
# Draw the text
|
||||
titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTCOLOR)
|
||||
titleRect.center = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2) - 3)
|
||||
DISPLAYSURF.blit(titleSurf, titleRect)
|
||||
|
||||
# Draw the additional "Press a key to play." text.
|
||||
pressKeySurf, pressKeyRect = makeTextObjs(
|
||||
'Press a key to play.', BASICFONT, TEXTCOLOR)
|
||||
pressKeyRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 100)
|
||||
DISPLAYSURF.blit(pressKeySurf, pressKeyRect)
|
||||
|
||||
while checkForKeyPress() == None:
|
||||
pygame.display.update()
|
||||
FPSCLOCK.tick()
|
||||
|
||||
|
||||
def checkForQuit():
|
||||
for event in pygame.event.get(QUIT): # get all the QUIT events
|
||||
terminate() # terminate if any QUIT events are present
|
||||
for event in pygame.event.get(KEYUP): # get all the KEYUP events
|
||||
if event.key == K_ESCAPE:
|
||||
terminate() # terminate if the KEYUP event was for the Esc key
|
||||
pygame.event.post(event) # put the other KEYUP event objects back
|
||||
|
||||
|
||||
def calculateLevelAndFallFreq(score):
|
||||
# Based on the score, return the level the player is on and
|
||||
# how many seconds pass until a falling piece falls one space.
|
||||
level = int(score / 10) + 1
|
||||
fallFreq = 0.27 - (level * 0.02)
|
||||
return level, fallFreq
|
||||
|
||||
|
||||
def getNewPiece():
|
||||
# return a random new piece in a random rotation and color
|
||||
shape = random.choice(list(PIECES.keys()))
|
||||
newPiece = {'shape': shape,
|
||||
'rotation': random.randint(0, len(PIECES[shape]) - 1),
|
||||
'x': int(BOARDWIDTH / 2) - int(TEMPLATEWIDTH / 2),
|
||||
'y': -2, # start it above the board (i.e. less than 0)
|
||||
'color': random.randint(0, len(COLORS) - 1)}
|
||||
return newPiece
|
||||
|
||||
|
||||
def addToBoard(board, piece):
|
||||
# fill in the board based on piece's location, shape, and rotation
|
||||
for x in range(TEMPLATEWIDTH):
|
||||
for y in range(TEMPLATEHEIGHT):
|
||||
if PIECES[piece['shape']][piece['rotation']][y][x] != BLANK:
|
||||
board[x + piece['x']][y + piece['y']] = piece['color']
|
||||
|
||||
|
||||
def getBlankBoard():
|
||||
# create and return a new blank board data structure
|
||||
board = []
|
||||
for i in range(BOARDWIDTH):
|
||||
board.append([BLANK] * BOARDHEIGHT)
|
||||
return board
|
||||
|
||||
|
||||
def isOnBoard(x, y):
|
||||
return x >= 0 and x < BOARDWIDTH and y < BOARDHEIGHT
|
||||
|
||||
|
||||
def isValidPosition(board, piece, adjX=0, adjY=0):
|
||||
# Return True if the piece is within the board and not colliding
|
||||
for x in range(TEMPLATEWIDTH):
|
||||
for y in range(TEMPLATEHEIGHT):
|
||||
isAboveBoard = y + piece['y'] + adjY < 0
|
||||
if isAboveBoard or PIECES[piece['shape']][piece['rotation']][y][x] == BLANK:
|
||||
continue
|
||||
if not isOnBoard(x + piece['x'] + adjX, y + piece['y'] + adjY):
|
||||
return False
|
||||
if board[x + piece['x'] + adjX][y + piece['y'] + adjY] != BLANK:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def isCompleteLine(board, y):
|
||||
# Return True if the line filled with boxes with no gaps.
|
||||
for x in range(BOARDWIDTH):
|
||||
if board[x][y] == BLANK:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def removeCompleteLines(board):
|
||||
# Remove any completed lines on the board, move everything above them down, and return the number of complete lines.
|
||||
numLinesRemoved = 0
|
||||
y = BOARDHEIGHT - 1 # start y at the bottom of the board
|
||||
while y >= 0:
|
||||
if isCompleteLine(board, y):
|
||||
# Remove the line and pull boxes down by one line.
|
||||
for pullDownY in range(y, 0, -1):
|
||||
for x in range(BOARDWIDTH):
|
||||
board[x][pullDownY] = board[x][pullDownY - 1]
|
||||
# Set very top line to blank.
|
||||
for x in range(BOARDWIDTH):
|
||||
board[x][0] = BLANK
|
||||
numLinesRemoved += 1
|
||||
# Note on the next iteration of the loop, y is the same.
|
||||
# This is so that if the line that was pulled down is also
|
||||
# complete, it will be removed.
|
||||
else:
|
||||
y -= 1 # move on to check next row up
|
||||
return numLinesRemoved
|
||||
|
||||
|
||||
def convertToPixelCoords(boxx, boxy):
|
||||
# Convert the given xy coordinates of the board to xy
|
||||
# coordinates of the location on the screen.
|
||||
return (XMARGIN + (boxx * BOXSIZE)), (TOPMARGIN + (boxy * BOXSIZE))
|
||||
|
||||
|
||||
def drawBox(boxx, boxy, color, pixelx=None, pixely=None):
|
||||
# draw a single box (each tetromino piece has four boxes)
|
||||
# at xy coordinates on the board. Or, if pixelx & pixely
|
||||
# are specified, draw to the pixel coordinates stored in
|
||||
# pixelx & pixely (this is used for the "Next" piece).
|
||||
if color == BLANK:
|
||||
return
|
||||
if pixelx == None and pixely == None:
|
||||
pixelx, pixely = convertToPixelCoords(boxx, boxy)
|
||||
pygame.draw.rect(DISPLAYSURF, COLORS[color],
|
||||
(pixelx + 1, pixely + 1, BOXSIZE - 1, BOXSIZE - 1))
|
||||
pygame.draw.rect(
|
||||
DISPLAYSURF, LIGHTCOLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 4, BOXSIZE - 4))
|
||||
|
||||
|
||||
def drawBoard(board):
|
||||
# draw the border around the board
|
||||
pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (XMARGIN - 3, TOPMARGIN - 7,
|
||||
(BOARDWIDTH * BOXSIZE) + 8, (BOARDHEIGHT * BOXSIZE) + 8), 5)
|
||||
|
||||
# fill the background of the board
|
||||
pygame.draw.rect(DISPLAYSURF, BGCOLOR, (XMARGIN, TOPMARGIN,
|
||||
BOXSIZE * BOARDWIDTH, BOXSIZE * BOARDHEIGHT))
|
||||
# draw the individual boxes on the board
|
||||
for x in range(BOARDWIDTH):
|
||||
for y in range(BOARDHEIGHT):
|
||||
drawBox(x, y, board[x][y])
|
||||
|
||||
|
||||
def drawStatus(score, level):
|
||||
# draw the score text
|
||||
scoreSurf = BASICFONT.render('Score: %s' % score, True, TEXTCOLOR)
|
||||
scoreRect = scoreSurf.get_rect()
|
||||
scoreRect.topleft = (WINDOWWIDTH - 150, 20)
|
||||
DISPLAYSURF.blit(scoreSurf, scoreRect)
|
||||
|
||||
# draw the level text
|
||||
levelSurf = BASICFONT.render('Level: %s' % level, True, TEXTCOLOR)
|
||||
levelRect = levelSurf.get_rect()
|
||||
levelRect.topleft = (WINDOWWIDTH - 150, 50)
|
||||
DISPLAYSURF.blit(levelSurf, levelRect)
|
||||
|
||||
|
||||
def drawPiece(piece, pixelx=None, pixely=None):
|
||||
shapeToDraw = PIECES[piece['shape']][piece['rotation']]
|
||||
if pixelx == None and pixely == None:
|
||||
# if pixelx & pixely hasn't been specified, use the location stored in the piece data structure
|
||||
pixelx, pixely = convertToPixelCoords(piece['x'], piece['y'])
|
||||
|
||||
# draw each of the boxes that make up the piece
|
||||
for x in range(TEMPLATEWIDTH):
|
||||
for y in range(TEMPLATEHEIGHT):
|
||||
if shapeToDraw[y][x] != BLANK:
|
||||
drawBox(None, None, piece['color'], pixelx
|
||||
+ (x * BOXSIZE), pixely + (y * BOXSIZE))
|
||||
|
||||
|
||||
def drawNextPiece(piece):
|
||||
# draw the "next" text
|
||||
nextSurf = BASICFONT.render('Next:', True, TEXTCOLOR)
|
||||
nextRect = nextSurf.get_rect()
|
||||
nextRect.topleft = (WINDOWWIDTH - 120, 80)
|
||||
DISPLAYSURF.blit(nextSurf, nextRect)
|
||||
# draw the "next" piece
|
||||
drawPiece(piece, pixelx=WINDOWWIDTH - 120, pixely=100)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue