Advertisement
cookertron

Tetris Clone using Python & Pygame

Oct 28th, 2018
380
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.43 KB | None | 0 0
  1. import math, random, sys, time
  2. import pygame
  3. import pygame.gfxdraw
  4. from pygame.locals import *
  5.  
  6. # define some colors
  7. FUCHSIA = (255,   0, 255)
  8. PURPLE  = (128,   0, 128)
  9. TEAL    = (  0, 128, 128)
  10. LIME    = (  0, 255,   0)
  11. GREEN   = (  0, 128,   0)
  12. OLIVE   = (128, 128,   0)
  13. YELLOW  = (255, 255,   0)
  14. ORANGE  = (255, 165,   0)
  15. RED     = (255,   0,   0)
  16. MAROON  = (128,   0,   0)
  17. SILVER  = (192, 192, 192)
  18. GREY    = (128, 128, 128)
  19. NIGHT   = (32,   32,  32)
  20. BLUE    = (  0,   0, 255)
  21. NAVY    = (  0,   0, 128)
  22. AQUA    = (  0, 255, 255)
  23. WHITE   = (255, 255, 255)
  24. BLACK   = (  0,   0,   0)
  25.  
  26. # define some data
  27. BLOCKS = {1: {'startingY' : [0, 2, 0, 2], 'block' : [[(-1, 0), (0, 0), (1, 0), (2, 0)], [(1, -2), (1, -1), (1, 0), (1, 1)], [(-1, 0), (0, 0), (1, 0), (2, 0)], [(0, -2), (0, -1), (0, 0), (0, 1)]]}, # long one
  28.           2: {'startingY' : [0, 1, 0, 1], 'block' : [[(-1, 0), (0, 0), (1, 0), (0, 1)], [(-1, 0), (0, -1), (0, 0), (0, 1)], [(0, 0), (-1, 1), (0, 1), (1, 1)], [(0, -1), (0, 0), (0, 1), (1, 0)]]}, # T shape
  29.           3: {'startingY' : [0, 1, 0, 1], 'block' : [[(-1, 1), (-1, 0), (0, 0), (1, 0)], [(-1, -1), (0, -1), (0, 0), (0, 1)], [(1, 0), (-1, 1), (0, 1), (1, 1)], [(0, -1), (0, 0), (0, 1), (1, 1)]]}, # L left
  30.           4: {'startingY' : [0, 1, 0, 1], 'block' : [[(-1, 0), (0, 0), (1, 0), (1, 1)], [(0, -1), (0, 0), (0, 1), (-1, 1)], [(-1, 0), (-1, 1), (0, 1), (1, 1)], [(0, -1), (0, 0), (0, 1), (1, -1)]]}, # L right
  31.           5: {'startingY' : [0, 1, 0, 1], 'block' : [[(-1, 0), (0, 0), (0, 1), (1, 1)], [(1, -1), (1, 0), (0, 0), (0, 1)], [(-1, 0), (0, 0), (0, 1), (1, 1)], [(0, -1), (0, 0), (-1, 0), (-1, 1)]]}, # lightening 1
  32.           6: {'startingY' : [0, 1, 0, 1], 'block' : [[(-1, 1), (0, 1), (0, 0), (1, 0)], [(0, -1), (0, 0), (1, 0), (1, 1)], [(-1, 1), (0, 1), (0, 0), (1, 0)], [(-1, -1), (-1, 0), (0, 0), (0, 1)]]}, # lightening 2
  33.           7: {'startingY' : [1], 'block' : [[(-1, -1), (0, -1), (-1, 0), (0, 0)]]}, # square
  34.  
  35.           }
  36.  
  37. BLOCK_COLORS = [BLACK, RED, TEAL, ORANGE, NAVY, PURPLE, GREEN, YELLOW]
  38.  
  39. SCORES = [40, 100, 300, 1200]
  40.  
  41. tetrisFont = {
  42.     'width' : 10,
  43.     'height' : 14,
  44.     'space' : 4,
  45.     'totalWidth' : 14,
  46.     'a' : [[0, 5, 5, 0], [5, 0, 10, 5], [10, 5, 10, 14], [0, 7, 10, 7], [0, 14, 0, 5]],
  47.     'b' : [[0, 0, 5, 0], [5, 0, 10, 4], [10, 4, 5, 7], [5, 7, 10, 10], [10, 10, 5, 14], [5, 14, 0, 14], [0, 14, 0, 0]],
  48.     'c' : [[0, 0, 10, 0], [0, 0, 0, 14], [0, 14, 10, 14]],
  49.     'd' : [[0, 0, 5, 0], [5, 0, 10, 5], [10, 5, 10, 9], [10, 9, 5, 14], [5, 14, 0, 14], [0, 14, 0, 0]],
  50.     'e' : [[0, 0, 10, 0], [0, 0, 0, 14], [0, 7, 8, 7], [0, 14, 10, 14]],
  51.     'f' : [[0, 0, 10, 0], [0, 0, 0, 14], [0, 7, 8, 7]],
  52.     'g' : [[0, 0, 10, 0], [0, 0, 0, 14], [0, 14, 10, 14], [10, 14, 10, 9], [10, 9, 8, 7]],
  53.     'h' : [[0, 0, 0, 14], [0, 7, 10, 7], [10, 0, 10, 14]],
  54.     'i' : [[0, 0, 10, 0], [5, 0, 5, 14], [0, 14, 10, 14]],
  55.     'j' : [[0, 9, 5, 14], [5, 14, 10, 14], [10, 14, 10, 0]],
  56.     'k' : [[0, 0, 0, 14], [0, 7, 10, 0], [0, 7, 10,14]],
  57.     'l' : [[0, 0, 0, 14], [0, 14, 10, 14]],
  58.     'm' : [[0, 14, 0, 0], [0, 0, 5, 5], [5, 5, 10, 0], [10, 0, 10, 14]],
  59.     'n' : [[0, 14, 0, 0], [0, 0, 10, 14], [10, 14, 10, 0]],
  60.     'o' : [[0, 0, 10, 0], [10, 0, 10, 14], [10, 14, 0, 14], [0, 14, 0, 0]],
  61.     'p' : [[0, 14, 0, 0], [0, 0, 10, 0], [10, 0, 10, 7], [10, 7, 0, 9]],
  62.     'q' : [[0, 0, 10, 0], [10, 0, 10, 9], [10, 9, 0, 14], [0, 14, 0, 0], [5, 9, 10, 14]],
  63.     'r' : [[0, 14, 0, 0], [0, 0, 10, 0], [10, 0, 10, 7], [10, 7, 0, 9], [5, 8, 10, 14]],
  64.     's' : [[0, 12, 3, 14], [3, 14, 10, 14], [10, 14, 10, 9], [10, 9, 0, 5], [0, 5, 0, 0], [0, 0, 7, 0], [7, 0, 10, 2]],
  65.     't' : [[0, 0, 10, 0], [5, 0, 5, 14]],
  66.     'u' : [[0, 0, 0, 9], [0, 9, 5, 14], [5, 14, 10,9], [10, 9, 10, 0]],
  67.     'v' : [[0, 0, 5, 14], [5, 14, 10, 0]],
  68.     'w' : [[0, 0, 3, 14], [3, 14, 5, 9], [5, 9, 7, 14], [7, 14, 10, 0]],
  69.     'x' : [[0, 0, 10, 14], [0, 14, 10, 0]],
  70.     'y' : [[0, 0, 5, 7], [10, 0, 5, 7], [5, 7, 5, 14]],
  71.     'z' : [[0, 0, 10, 0], [10, 0, 0, 14], [0, 14, 10, 14]],
  72.     '0' : [[0, 0, 10, 0], [10, 0, 10, 14], [10, 14, 0, 14], [0, 14, 0, 0], [0, 14, 10, 0]],
  73.     '1' : [[3, 3, 5, 0], [5, 0, 5, 14]],
  74.     '2' : [[0, 0, 10, 0], [10, 0, 10, 5], [10, 5, 0, 9], [0, 9, 0, 14], [0, 14, 10, 14]],
  75.     '3' : [[0, 0, 10, 0], [10, 0, 10, 14], [10, 14, 0, 14], [0, 7, 10, 7]],
  76.     '4' : [[0, 0, 0, 7], [0, 7, 10, 7], [10, 0, 10, 14]],
  77.     '5' : [[10, 0, 0, 0], [0, 0, 0, 5], [0, 5, 10, 9], [10, 9, 10, 14], [10, 14, 0, 14]],
  78.     '6' : [[0, 0, 0, 14], [0, 14, 10, 14], [10, 14, 10, 9], [10, 9, 0, 5]],
  79.     '7' : [[0, 0, 10, 0], [10, 0, 10, 7], [10, 7, 5, 14]],
  80.     '8' : [[0, 0, 10, 0], [10, 0, 10, 14], [10, 14, 0, 14], [0, 14, 0, 0], [0, 7, 10, 7]],
  81.     '9' : [[10, 14, 10, 0], [10, 0, 0, 0], [0, 0, 0, 5], [0, 5, 10, 9]],
  82.     ':' : [[5, 3, 5, 5], [5, 9, 5, 11]],
  83.     '.' : [[5, 11, 5, 14]],
  84.     '!' : [[5, 0, 5, 7], [5, 11, 5, 14]],
  85. }
  86.  
  87. # exit the program
  88. def quit():
  89.     for event in pygame.event.get():
  90.         if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
  91.             return True
  92.     return False
  93.  
  94.    
  95. # this function draws a string to the display surface using the font line data
  96. def fontSurface(font, text, scale, fgColor):
  97.     width = math.ceil(len(text) * (font['totalWidth'] * scale))
  98.     height = math.ceil(font['height'] * scale)
  99.  
  100.     surface = pygame.Surface((width, height + 1))
  101.     offset = 0
  102.     for char in text:
  103.         if char != ' ':
  104.             lines = font[char]
  105.             for line in lines:
  106.                 sx, sy, ex, ey = (line[0] * scale) + offset, (line[1] * scale), (line[2] * scale) + offset, (line[3] * scale)
  107.                 pygame.draw.line(surface, fgColor, (sx, sy), (ex, ey), 1)
  108.         offset += font['totalWidth'] * scale
  109.     surface.set_colorkey(0)
  110.     return surface
  111.  
  112. def miniBlock(x, y, blockID):
  113.     global BLOCK_COLORS, BLOCKS
  114.    
  115.     surface = pygame.display.get_surface()
  116.    
  117.     block = BLOCKS[blockID]['block'][0]
  118.     for bit in block:
  119.         pygame.draw.rect(surface, BLOCK_COLORS[blockID], (x + bit[0] * 7, y + bit[1] * 7, 7, 7), 0)
  120.    
  121. # draw a patterned title
  122. class tiles:
  123.     def __init__(self):
  124.         global NIGHT, W, H
  125.        
  126.         self.tileSize = 32
  127.         self.tile = pygame.Surface((self.tileSize, self.tileSize))
  128.        
  129.         for linePos in range(0, self.tileSize * 2, 8):
  130.             pygame.draw.line(self.tile, NIGHT, (linePos, 0), (linePos - self.tileSize, self.tileSize), 1)
  131.        
  132.         #self.tile.convert()       
  133.         self.displayWidthInTiles = (W / self.tileSize) * self.tileSize
  134.         if W % self.tileSize > 0: self.displayWidthInTiles += self.tileSize
  135.  
  136.         self.displayHeightInTiles = (H / self.tileSize) * self.tileSize
  137.         if H % self.tileSize > 0: self.displayHeightInTiles += self.tileSize
  138.  
  139.     def fillDisplay(self, displaySurf):
  140.         global AREAS, W, H
  141.         for tilePosY in range(0, self.displayHeightInTiles, self.tileSize):
  142.             for tilePosX in range(0, self.displayWidthInTiles, self.tileSize):
  143.                 displaySurf.blit(self.tile, (tilePosX, tilePosY))
  144.         AREAS.append((0, 0, W, H))
  145.        
  146. class blockZone:
  147.     def __init__(self):
  148.         global BLOCKS, W, H
  149.        
  150.         #define the area in which the blocks fall and stack in
  151.         self.height = (int(H * 0.9722222222222222) / 20) * 20
  152.         self.topMargin = (H - self.height) / 2     
  153.  
  154.         self.blockSize = int(float(self.height) / 20)
  155.        
  156.         self.width = self.blockSize * 10
  157.         self.leftMargin = (W - self.width) / 2
  158.        
  159.         self.bzSurf = pygame.Surface((self.width, self.height))
  160.    
  161.         self.stack = [[0 for x in range(10)] for y in range(20)]
  162.        
  163.         self.currentBlock = random.randint(1, 7)
  164.         self.rotation = 0
  165.         self.blockPosX = 5
  166.         self.blockPosY = BLOCKS[self.currentBlock]['startingY'][self.rotation]
  167.         self.nextBlock = random.randint(1, 7)
  168.        
  169.     def blitToDisplay(self, displaySurf):
  170.         global AREAS
  171.         displaySurf.blit(self.bzSurf, (self.leftMargin, self.topMargin))
  172.         AREAS.append((self.leftMargin, self.topMargin, self.width, self.height))
  173.    
  174.     def drawStack(self):
  175.         global BLOCK_COLORS
  176.         for x in range(10):
  177.             for y in range(20):
  178.                 pygame.draw.rect(self.bzSurf, BLOCK_COLORS[self.stack[y][x]], (x * self.blockSize, y * self.blockSize, self.blockSize, self.blockSize), 0)
  179.        
  180.     def drawBlock(self, delete = False):
  181.         global BLOCKS, BLOCK_COLORS
  182.        
  183.         block = BLOCKS[self.currentBlock]['block'][self.rotation]
  184.         if delete:
  185.             blockID = 0
  186.         else:
  187.             blockID = self.currentBlock
  188.            
  189.         for bit in block:
  190.             y = self.blockPosY + bit[1]
  191.             self.stack[y][self.blockPosX + bit[0]] = blockID
  192.    
  193.     def fall(self):
  194.         global BLOCKS, previousScore
  195.         self.drawBlock(True)
  196.         if self.collisionOnFall():
  197.             self.drawBlock()
  198.            
  199.             # test for complete lines
  200.             self.testForFullLines()
  201.            
  202.             self.currentBlock = self.nextBlock
  203.             self.nextBlock = random.randint(1, 7)
  204.             self.rotation = 0
  205.             self.blockPosY = BLOCKS[self.currentBlock]['startingY'][0]
  206.             self.blockPosX = 5
  207.             self.drawBlock()
  208.             previousScore = -1
  209.             SPF = 0
  210.             # test for collision on spawn
  211.            
  212.         else:
  213.             self.blockPosY += 1
  214.             self.drawBlock()
  215.    
  216.     def collisionOnFall(self):
  217.         global BLOCKS
  218.         block = BLOCKS[self.currentBlock]['block'][self.rotation]
  219.        
  220.         tempY = self.blockPosY + 1
  221.         for bit in block:
  222.             x = self.blockPosX + bit[0]
  223.             y = tempY + bit[1]
  224.             if y > 19: return True
  225.             if self.stack[y][x] > 0: return True
  226.            
  227.         return False
  228.    
  229.     def collisionOnMove(self, rotate = True):
  230.         global BLOCKS
  231.        
  232.         if rotate:
  233.             block = BLOCKS[self.currentBlock]['block'][(self.rotation + 1) % len(BLOCKS[self.currentBlock]['block'])]
  234.         else:
  235.             block = BLOCKS[self.currentBlock]['block'][self.rotation]
  236.            
  237.         for bit in block:
  238.             x = self.blockPosX + bit[0]
  239.             y = self.blockPosY + bit[1]
  240.             if x > 9 or x < 0 or y > 19 or y < 0: return True
  241.             if self.stack[y][x] > 0: return True
  242.        
  243.         return False
  244.    
  245.     def testForFullLines(self):
  246.         global SCORES, score, level, lines, FPS, SPF
  247.        
  248.         popList = []
  249.         for y in range(19, 0, -1):
  250.             if 0 in self.stack[y]:
  251.                 continue
  252.             popList.append(y)
  253.         if popList:
  254.             for y in popList:
  255.                 self.stack.pop(y)
  256.             for y in popList:
  257.                 self.stack.insert(0, [0 for x in range(10)])
  258.             score += SCORES[len(popList) - 1] * level
  259.             lines += len(popList)
  260.             if lines >= 10:
  261.                 lines %= 10
  262.                 level += 1
  263.                 FPS += 0.3
  264.                
  265.     def rotateBlock(self):
  266.         global BLOCKS
  267.         if not self.collisionOnMove():
  268.             self.rotation += 1
  269.             self.rotation %= len(BLOCKS[self.currentBlock]['block'])
  270.    
  271. # define display surface           
  272. W, H = 1280, 720
  273. HW, HH = W / 2, H / 2
  274.  
  275. # initialise display
  276. pygame.init()
  277. FONT = pygame.font.SysFont(None, 72)
  278. DS = pygame.display.set_mode((W, H))
  279. pygame.display.set_caption("Tetris Clone")
  280. FPS = 1
  281. SPF = 1.00 / FPS
  282.  
  283. # the areas of the display that need updating
  284. AREAS = []
  285.  
  286. # create the background tiles instance
  287. TILES = tiles()
  288. TILES.fillDisplay(DS)
  289.  
  290. BZ = blockZone()
  291. BZ.drawBlock()
  292.  
  293. # draw the tetris logo against the side of the stack window
  294. logo = fontSurface(tetrisFont, "tetris", 8, WHITE)
  295. verticalLogo = pygame.transform.rotate(logo, 90)
  296. DS.blit(verticalLogo, (BZ.leftMargin - 10 - verticalLogo.get_rect().width, 10))
  297.  
  298. # copy a section of the background for the score and level
  299. scorePosX = BZ.leftMargin + BZ.width + 10
  300. scoreRect = (scorePosX, BZ.topMargin, 48, BZ.height)
  301. scoreBackground = pygame.Surface(scoreRect[2:4])
  302. scoreBackground.blit(DS, (0, 0), scoreRect)
  303.  
  304. # track the score
  305. previousScore = -1
  306. score = 0
  307.  
  308. # track the level
  309. level = 1
  310.  
  311. # track number of lines completed
  312. lines = 0
  313.  
  314. # monitor key held
  315. keyHeld = False
  316.  
  317. # start FPS monitoring
  318. FPSTime = time.time()
  319.  
  320. # main loop
  321. while not quit():
  322.     k = pygame.key.get_pressed()
  323.    
  324.     if k[K_DOWN]:
  325.         SPF = 0.05
  326.     else:
  327.         SPF = 1.00 / FPS
  328.  
  329.     if k[K_LEFT] and not keyHeld:
  330.         BZ.drawBlock(True)         
  331.         BZ.blockPosX -= 1
  332.         if BZ.collisionOnMove(False):
  333.             BZ.blockPosX += 1
  334.         BZ.drawBlock()
  335.    
  336.     if k[K_RIGHT] and not keyHeld:
  337.         BZ.drawBlock(True)         
  338.         BZ.blockPosX += 1
  339.         if BZ.collisionOnMove(False):
  340.             BZ.blockPosX -= 1
  341.         BZ.drawBlock()
  342.    
  343.     if k[K_UP] and not keyHeld:
  344.         BZ.drawBlock(True)
  345.         BZ.rotateBlock()
  346.         BZ.drawBlock()
  347.        
  348.     if k[K_RETURN]:
  349.         pygame.image.save(DS, "screenshot.png")
  350.         pygame.quit()
  351.         sys.exit()
  352.  
  353.     # when a key is pressed it has to be released and pressed again to repeat action
  354.     if True in k[273:277]:
  355.         keyHeld = True
  356.     else:
  357.         keyHeld = False
  358.  
  359.     BZ.drawStack()
  360.     BZ.blitToDisplay(DS)
  361.  
  362.     if score != previousScore:
  363.         previousScore = score
  364.         scoreSurf = fontSurface(tetrisFont, "score: {0}  level: {1}".format(score, level), 2, WHITE)
  365.        
  366.         DS.blit(scoreBackground, (scorePosX, BZ.topMargin))
  367.         DS.blit(pygame.transform.rotate(scoreSurf, -90), (scorePosX + 10, BZ.topMargin + 48))
  368.         miniBlock(scorePosX + 17, BZ.topMargin + 10, BZ.nextBlock)
  369.         AREAS.append(scoreRect)
  370.    
  371.     if time.time() > FPSTime + SPF:
  372.         # test for collision
  373.         BZ.fall()
  374.         FPSTime = time.time()
  375.    
  376.     pygame.display.update(AREAS)
  377.     AREAS = []
  378.        
  379.     #DS.fill(BLACK)
  380. pygame.quit()
  381. sys.exit()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement