summaryrefslogtreecommitdiff
path: root/tetris/tetris.py
diff options
context:
space:
mode:
Diffstat (limited to 'tetris/tetris.py')
-rwxr-xr-xtetris/tetris.py612
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()