cookertron

Python Pygame Tetris Clone

Apr 22nd, 2021
526
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.48 KB | None | 0 0
  1. import sys, time, random, os, base64
  2. import pygame
  3.  
  4. BLACK = (0, 0, 0)
  5. WHITE = (255, 255, 255)
  6. RED = (128, 0, 0)
  7. GREEN = (0, 128, 0)
  8. BLUE = (0, 0, 128)
  9. DARK_GRAY = (32, 32, 32)
  10. LOVELY_BLUE = (22, 54, 83)
  11.  
  12. SPRITE_DATA_FILENAME = "blocks.png"
  13. SPRITE_DATA =   b'iVBORw0KGgoAAAANSUhEUgAAACoAAAAGCAMAAACCc/wkAAAAAXNSR0IArs4c6QAAAGBQTFRFAAA\
  14.                AIiA0RSg8Zjkxj1Y733Em2aBm7sOa+/I2meVQar4wN5RuS2kvUkskMjw5Pz90MGCCW27hY5v/X83k\
  15.                y9v8////m623hH6HaWpqWVZSdkKKrDIy2Vdj13u6j5dKim8w+2O8zwAAACB0Uk5TAP///////////\
  16.                /////////////////////////////+Smq12AAAAPklEQVQYlWMQJRowiDIAAYRGIxmgJEQVhIDwEB\
  17.                KiUA1gBFYFUQYXYhBF0oBQCrMHyQEMokgkQgOSCxiI9xYACEgN8+zH0H8AAAAASUVORK5CYII='
  18.  
  19. SHAPES = [
  20.     [
  21.         [
  22.             [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0] # O
  23.         ]
  24.     ],
  25.     [
  26.         [
  27.             [0, 0, 0, 0], [2, 2, 2, 2], [0, 0, 0, 0], [0, 0, 0, 0] # I
  28.         ],
  29.         [
  30.             [0, 0, 2, 0], [0, 0, 2, 0], [0, 0, 2, 0], [0, 0, 2, 0]
  31.         ]
  32.     ],
  33.     [
  34.         [
  35.             [0, 0, 0, 0], [0, 0, 3, 3], [0, 3, 3, 0], [0, 0, 0, 0] # S
  36.         ],
  37.         [
  38.             [0, 0, 3, 0], [0, 0, 3, 3], [0, 0, 0, 3], [0, 0, 0, 0]
  39.         ]        
  40.     ],
  41.     [
  42.         [
  43.             [0, 0, 0, 0], [0, 4, 4, 0], [0, 0, 4, 4], [0, 0, 0, 0] # Z
  44.         ],
  45.         [
  46.             [0, 0, 0, 4], [0, 0, 4, 4], [0, 0, 4, 0], [0, 0, 0, 0]
  47.         ]        
  48.     ],    
  49.     [
  50.         [
  51.             [0, 0, 0, 0], [0, 5, 5, 5], [0, 5, 0, 0], [0, 0, 0, 0] # L
  52.         ],
  53.         [
  54.             [0, 0, 5, 0], [0, 0, 5, 0], [0, 0, 5, 5], [0, 0, 0, 0]
  55.         ],
  56.         [
  57.             [0, 0, 0, 5], [0, 5, 5, 5], [0, 0, 0, 0], [0, 0, 0, 0]
  58.         ],
  59.         [
  60.             [0, 5, 5, 0], [0, 0, 5, 0], [0, 0, 5, 0], [0, 0, 0, 0]
  61.         ]
  62.     ],
  63.     [
  64.         [
  65.             [0, 0, 0, 0], [0, 6, 6, 6], [0, 6, 0, 0], [0, 0, 0, 0] # J
  66.         ],
  67.         [
  68.             [0, 0, 6, 6], [0, 0, 6, 0], [0, 0, 6, 0], [0, 0, 0, 0]
  69.         ],
  70.         [
  71.             [0, 6, 0, 0], [0, 6, 6, 6], [0, 0, 0, 0], [0, 0, 0, 0]
  72.         ],
  73.         [
  74.             [0, 0, 6, 0], [0, 0, 6, 0], [0, 6, 6, 0], [0, 0, 0, 0]
  75.         ]
  76.     ],
  77.     [
  78.         [
  79.             [0, 0, 0, 0], [0, 7, 7, 7], [0, 0, 7, 0], [0, 0, 0, 0] # T
  80.         ],
  81.         [
  82.             [0, 0, 7, 0], [0, 0, 7, 7], [0, 0, 7, 0], [0, 0, 0, 0]
  83.         ],
  84.         [
  85.             [0, 0, 7, 0], [0, 7, 7, 7], [0, 0, 0, 0], [0, 0, 0, 0]
  86.         ],
  87.         [
  88.             [0, 0, 7, 0], [0, 7, 7, 0], [0, 0, 7, 0], [0, 0, 0, 0]
  89.         ]
  90.     ],        
  91. ]
  92.  
  93. # handy way of working with coordinate integers
  94. class vect:
  95.     def __init__(s, x, y = None):
  96.         if type(x) is tuple:
  97.             s.x = int(x[0])
  98.             s.y = int(x[1])
  99.         else:
  100.             s.x = int(x)
  101.             if y == None:
  102.                 s.y = s.x
  103.             else:
  104.                 s.y = int(y)
  105.         s.v = (s.x, s.y)
  106.    
  107.     def __add__(s, n):
  108.         if type(n) is tuple:
  109.             return vect(s.x + n[0], s.y + n[1])
  110.         elif type(n) is vect:
  111.             return vect(s.x + n.x, s.y + n.y)
  112.         else:
  113.             return vect(s.x + n, s.y + n)
  114.  
  115.     def __sub__(s, n):
  116.         if type(n) is tuple:
  117.             return vect(s.x - n[0], s.y - n[1])
  118.         elif type(n) is vect:
  119.             return vect(s.x - n.x, s.y - n.y)
  120.         else:
  121.             return vect(s.x - n, s.y - n)
  122.  
  123.     def __mul__(s, n):
  124.         if type(n) is tuple:
  125.             return vect(s.x * n[0], s.y * n[1])
  126.         elif type(n) is vect:
  127.             return vect(s.x * n.x, s.y * n.y)
  128.         else:
  129.             return vect(s.x * n, s.y * n)
  130.  
  131.     def __truediv__(s, n):
  132.         if type(n) is tuple:
  133.             return vect(s.x / n[0], s.y / n[1])
  134.         elif type(n) is vect:
  135.             return vect(s.x / n.x, s.y / n.y)
  136.         else:
  137.             return vect(s.x / n, s.y / n)
  138.  
  139.     def __str__(s):
  140.         return "({}, {})".format(s.x, s.y)
  141.  
  142.     def setX(s, x):
  143.         s.x = x
  144.         s.v = (x, s.y)
  145.    
  146.     def setY(s, y):
  147.         s.y = y
  148.         s.v = (s.x, y)
  149.  
  150. class spritesheet:
  151.     def __init__(s, surface, cols, rows, colorkey = None):
  152.         # sprite sheet surface
  153.         s.sheet = surface
  154.        
  155.         # get width and height
  156.         s.sheetSize = vect(surface.get_rect().size)
  157.        
  158.         # n rows and n columns
  159.         s.grid = vect(cols, rows)
  160.        
  161.         # list to references the subsurfaces of the spritesheet.
  162.         s.sprites = []
  163.  
  164.         # size of each individual sprite
  165.         s.cellSize = s.sheetSize / s.grid
  166.  
  167.         # iterate through spritesheet to define individual subsurfaces
  168.         for y in range(rows):
  169.             for x in range(cols):
  170.                 # get position of each sprite
  171.                 pos = vect(x, y) * s.cellSize
  172.                
  173.                 # add subsprite to the list
  174.                 s.sprites.append(surface.subsurface((pos.v, s.cellSize.v)))
  175.  
  176.     # return a sprite by id
  177.     def get(s, id):
  178.         return s.sprites[id]
  179.  
  180.  
  181. class shape:
  182.     def __init__(s, id):
  183.         global SHAPES
  184.  
  185.         # shape id
  186.         s.id = id
  187.  
  188.         # rotation
  189.         s.r = 0
  190.  
  191.         # maxium number of available shape rotations
  192.         s.maxr = len(SHAPES[id])
  193.  
  194.         # if the top row of the shape is blank then y = -1
  195.         if sum(SHAPES[id][0][0]):
  196.             s.pos = vect(3, 0)
  197.         else:
  198.             s.pos = vect(3, -1)
  199.  
  200.     def draw(s):
  201.         global PAS # play area surface
  202.         global SHAPES
  203.  
  204.         for y in range(4):
  205.             for x in range(4):
  206.                 bid = SHAPES[s.id][s.r][y][x] # block sprite id
  207.                 # blit sprite to play area surface if bid > 0
  208.                 if bid:
  209.                     PAS.blit(SPRITES.sprites[bid - 1], ((s.pos + vect(x, y)) * SPRITES.cellSize).v)
  210.  
  211.     def collision(s, offset, rOffset = None):
  212.         global PLAY_AREA, PLAY_AREA_SIZE, SHAPES
  213.  
  214.         # check if collision involves shape rotation
  215.         if rOffset == None:
  216.             # grab a copy of the shape data if using current rotation value
  217.             shapeData = SHAPES[s.id][s.r]
  218.         else:
  219.             # adjust rotation if it has gone out of bounds
  220.             rotation = s.r + rOffset
  221.             if rotation >= s.maxr: rotation = 0
  222.             elif rotation < 0: rotation = s.maxr - 1
  223.  
  224.             # grab a copy of the shape data using new rotation value
  225.             shapeData = SHAPES[s.id][rotation]
  226.  
  227.         # iterate throught the shape data to determine:
  228.         # 1. has the shape collided with the wall?
  229.         # 2. has the shape collided with the floor?
  230.         # 3. does the shape rotate out of the ceiling?
  231.         # 3. has the shape collided with the stack?
  232.  
  233.         for y in range(4):
  234.             for x in range(4):
  235.                 # calculate the absolute position of each block within the shape.
  236.                 pos = s.pos + vect(x, y) + offset
  237.  
  238.                 # if the shape data is a block ie has a value >0
  239.                 if shapeData[y][x]:
  240.                     if pos.x < 0 or pos.x > PLAY_AREA_SIZE.x - 1: return "WALL"
  241.                     if pos.y > PLAY_AREA_SIZE.y - 1: return "FLOOR"
  242.                     if pos.y < 0: return "CEILING"
  243.                     if PLAY_AREA[pos.y][pos.x]: return "STACK"
  244.        
  245.         # no collision detected
  246.         return "OK"
  247.  
  248.     # add the shape to play areas data
  249.     def solidify(s):
  250.         global SHAPES, PLAY_AREA
  251.         for y in range(4):
  252.             for x in range(4):
  253.                 # get the block id from the shape data
  254.                 bid = SHAPES[s.id][s.r][y][x]
  255.  
  256.                 # if the block data >0
  257.                 if bid:
  258.                     # get the absolution position of the shape's block in the play area
  259.                     pos = s.pos + (x, y)
  260.                     # copy the block data to the play area
  261.                     PLAY_AREA[pos.y][pos.x] = bid
  262.  
  263.     # increment the rotation clockwise by one
  264.     def rotate(s):
  265.         s.r += 1
  266.         if s.r >= s.maxr: s.r = 0
  267.        
  268.  
  269. # start pygame and create primary display surface (PDS)
  270. pygame.init()
  271. PDR = pygame.Rect(0, 0, 1024, 720)
  272. PDS = pygame.display.set_mode(PDR.size)
  273. FPS = 30
  274.  
  275. # check if the blocks.png exists. If not then create it
  276. if not os.path.exists(SPRITE_DATA_FILENAME):
  277.     print("Writing title image...", end = "")
  278.     data = base64.b64decode(SPRITE_DATA)
  279.     r = open(SPRITE_DATA_FILENAME, "bw")
  280.     r.write(data)
  281.     r.close()
  282.     print("Done!")
  283.  
  284. # load the spritesheet
  285. BLOCK_SPRITES = pygame.image.load("blocks.png").convert_alpha()
  286. SPRITES = spritesheet(BLOCK_SPRITES, 7, 1)
  287.  
  288. # define the scale of the graphics
  289. SCALE = vect(5)
  290.  
  291. # define the size in blocks of the play area
  292. PLAY_AREA_SIZE = vect(10, 20)
  293.  
  294. # define the play area data. A 2 dimensional list
  295. PLAY_AREA = [[0] * PLAY_AREA_SIZE.x for y in range(PLAY_AREA_SIZE.y)]
  296.  
  297. # the size of play area surface in pixels
  298. PLAY_AREA_PIXEL_SIZE = PLAY_AREA_SIZE * SPRITES.cellSize
  299.  
  300. # scaled size of the play area in pixels
  301. SCALED_PLAY_AREA_PIXEL_SIZE = PLAY_AREA_PIXEL_SIZE * SCALE
  302. SCALED_PLAY_AREA_PLACEMENT = (vect(PDR.size) - SCALED_PLAY_AREA_PIXEL_SIZE) / 2
  303.  
  304. # create play area surface
  305. PAS = pygame.Surface(PLAY_AREA_PIXEL_SIZE.v)
  306.  
  307. # define the shape
  308. currentShape = shape(random.randint(0, 6))
  309.  
  310. # drop down one line timer
  311. TIMER_DURATION = 10
  312. timer = TIMER_DURATION
  313.  
  314. # 85
  315. # prevent the shape spining like a catherine wheel if the up key if held down
  316. rotateKeyDown = False
  317.  
  318. while True:
  319.     events = pygame.event.get()
  320.     for e in events:
  321.         if e.type == pygame.QUIT: sys.exit()
  322.  
  323.     # clear the play area
  324.     PAS.fill(LOVELY_BLUE)
  325.  
  326.     # draw the current shape
  327.     currentShape.draw()
  328.  
  329.     # translate the play area data into sprites IDs to blit of the play area surface
  330.     for y in range(PLAY_AREA_SIZE.y):
  331.         for x in range(PLAY_AREA_SIZE.x):
  332.  
  333.             # get data from the play area located at y,x            
  334.             id = PLAY_AREA[y][x]
  335.  
  336.             # if the data in the play area list is >0 then translate to sprite ID and blit
  337.             if id:
  338.                 PAS.blit(SPRITES.get(id - 1), (vect(x, y) * SPRITES.cellSize).v)
  339.  
  340.     # update the display
  341.     PDS.blit(pygame.transform.scale(PAS, SCALED_PLAY_AREA_PIXEL_SIZE.v), SCALED_PLAY_AREA_PLACEMENT.v)
  342.     pygame.display.update()
  343.     pygame.time.Clock().tick(FPS)
  344.  
  345.     # decrement the drop timer
  346.     timer -= 1
  347.  
  348.     # if the drop timer reaches zero set the y velocity to 1 (ie drops one line)
  349.     if timer == 0:
  350.         timer = TIMER_DURATION
  351.         collisionResults = currentShape.collision((0, 1))
  352.         if collisionResults == "FLOOR" or collisionResults == "STACK":
  353.             # add the shape the play area's data
  354.             currentShape.solidify()
  355.             # change the shape
  356.             currentShape = shape(random.randint(0, 6))
  357.             continue
  358.         # move the shape down if no collision with the stack
  359.         currentShape.pos += (0, 1)
  360.  
  361.     # get key presses and move/rotate the current shape
  362.     k = pygame.key.get_pressed()
  363.     if k[pygame.K_RIGHT]:
  364.         if currentShape.collision((1, 0)) == "OK":
  365.             currentShape.pos += (1, 0)
  366.     elif k[pygame.K_LEFT]:
  367.         if currentShape.collision((-1, 0)) == "OK":
  368.             currentShape.pos += (-1, 0)
  369.     if k[pygame.K_UP] and not rotateKeyDown:
  370.         rotateKeyDown = True
  371.         if currentShape.collision((0, 0), 1) == "OK":
  372.             currentShape.rotate()
  373.     elif not k[pygame.K_UP] and rotateKeyDown:
  374.         rotateKeyDown = False
  375.    
  376. pygame.quit()
  377. sys.exit()
Add Comment
Please, Sign In to add comment