diff options
Diffstat (limited to 'tetris/tetris.py')
-rwxr-xr-x | tetris/tetris.py | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/tetris/tetris.py b/tetris/tetris.py new file mode 100755 index 0000000..b03689e --- /dev/null +++ b/tetris/tetris.py @@ -0,0 +1,612 @@ +#!/usr/bin/python3 +# Tetromino (a Tetris clone) +# By Al Sweigart al@inventwithpython.com +# http://inventwithpython.com/pygame +# Released under a "Simplified BSD" license + +# KRT 17/06/2012 rewrite event detection to deal with mouse use +# mods for joystick use by Robert Scheibe 18.06.2020 + +import random, time, pygame, sys +from pygame.locals import * + +#joystick or gamepad init +pygame.joystick.init() +print ("Joystics: ", pygame.joystick.get_count()) +js = pygame.joystick.Joystick(0) +js.init() + + +FPS = 25 +WINDOWWIDTH = 640 +WINDOWHEIGHT = 480 +BOXSIZE = 20 +BOARDWIDTH = 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) +GRAY1 = (240, 240, 240) +GRAY2 = (200, 200, 200) +GRAY3 = (160, 160, 160) +GRAY4 = (120, 120, 120) +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) +COLORS = ( GRAY1, GRAY2, GRAY3, GRAY4) +#LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW) +LIGHTCOLORS = ( GRAY1, GRAY2, GRAY3, GRAY4) +assert len(COLORS) == len(LIGHTCOLORS) # each color must have light color + +#TEMPLATEWIDTH = i, 5 +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() + pygame.mouse.set_visible(False) + + FPSCLOCK = pygame.time.Clock() + #DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) + DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),FULLSCREEN) + BASICFONT = pygame.font.Font('freesansbold.ttf', 18) + BIGFONT = pygame.font.Font('freesansbold.ttf', 100) + pygame.display.set_caption('Tetromino') + + showTextScreen('-=TETЯIS=-') + 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 + #key events + 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 + + #joystick events + elif (event.type == JOYBUTTONDOWN): + if (js.get_button(9) == 1): + # 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() + + # move the current piece all the way down with Button A + elif (js.get_button(1) == 1): + movingDown = False + movingLeft = False + movingRight = False + for i in range(1, BOARDHEIGHT): + if not isValidPosition(board, fallingPiece, adjY=i): + break + fallingPiece['y'] += i - 1 + + #rotate with button B + elif (js.get_button(0) == 1): + 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.type == JOYAXISMOTION): + # moving the piece sideways + if (js.get_axis(0) < 0 and isValidPosition(board, fallingPiece, adjX=-1)): + fallingPiece['x'] -= 1 + movingLeft = True + movingRight = False + lastMoveSidewaysTime = time.time() + + elif ((js.get_axis(0) > 0) 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 (js.get_axis(1) < 0): + fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']]) + if not isValidPosition(board, fallingPiece): + fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) + #elif (js.get_axis(1) > 0): # 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 (js.get_axis(1) > 0): + movingDown = True + if isValidPosition(board, fallingPiece, adjY=1): + fallingPiece['y'] += 1 + lastMoveDownTime = time.time() + + elif (js.get_axis(0)== 0.0): + movingLeft = False + movingRight = False + movingDown = False + + # 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) + 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() + + +# KRT 17/06/2012 rewrite event detection to deal with mouse use +def checkForKeyPress(): + for event in pygame.event.get(): + if event.type == QUIT: #event is quit + terminate() + elif event.type == KEYDOWN: + if event.key == K_ESCAPE: #event is escape key + terminate() + else: + return event.key #key found return with it + elif event.type == JOYBUTTONDOWN or event.type == JOYAXISMOTION: + if (js.get_button(8) == 1): + terminate() + return 1 + # no quit or key events in queue so return None + return None + + + +##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. \'Select\' to quit.', 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() |