5K
I was playing with python and pygame and decided to make a variant of the Tetris game (called KAtrix). This one is a little easier because the shapes do not fall automatically from the top. You have to move them with the left, right and down cursor keys and confirm the position with the space key.
Here is the screenshot and the source.
git clone https://github.com/klimenta/KAtrix
Not well documented, but it gives an idea what’s going on.
__author__ = 'Kliment Andreev' __version__ = '20151108 15:48' # Imports import random import time import pygame import sys from pygame.locals import * # Constants and dictionaries # Frames per second FPS = 25 # The top left column where the falling shape is positioned initially START_COL = 4 # Represent a box or empty space for a shape EMPTY_BOX = '-' FULL_BOX = 'X' # Window dimensions WINDOW_WIDTH = 640 WINDOW_HEIGHT = 480 # Matrix dimensions, including 4 borders (left, right, top, bottom) MATRIX_WIDTH = 12 MATRIX_HEIGHT = 22 # Fill the matrix with zeros MATRIX = [[0 for x in range(MATRIX_WIDTH)] for x in range(MATRIX_HEIGHT)] #Box width in pixels. Each shape is composed of boxes. BOX_SIZE = 20 # Define 16 web colors BLACK = (0x00, 0x00, 0x00) YELLOW = (0xff, 0xff, 0x00) WHITE = (0xff, 0xff, 0xff) SILVER = (0xc0, 0xc0, 0xc0) GRAY = (0x80, 0x80, 0x80) RED = (0xff, 0x00, 0x00) MAROON = (0x80, 0x00, 0x00) OLIVE = (0x80, 0x80, 0x00) LIME = (0x00, 0xff, 0x00) GREEN = (0x00, 0x80, 0x00) AQUA = (0x00, 0xff, 0xff) TEAL = (0x00, 0x80, 0x80) BLUE = (0x00, 0x00, 0xff) NAVY = (0x00, 0x00, 0x80) FUCHSIA = (0xff, 0x00, 0xff) PURPLE = (0x80, 0x00, 0x80) # Top left coordinates of the matrix TOP_X = 199 TOP_Y = 19 # Top left coordinates of the the box at (0,0) # This is the top left coordinate of the top-left shape can be TOP_BOX_X = TOP_X + BOX_SIZE TOP_BOX_Y = TOP_Y + BOX_SIZE # These are the shapes # Each line represent an original position # and three possible rotations SHAPE_I = (("----"), ("XXXX"), ("----"), ("----")), \ (("-X--"), ("-X--"), ("-X--"), ("-X--")), \ (("----"), ("XXXX"), ("----"), ("----")), \ (("-X--"), ("-X--"), ("-X--"), ("-X--")) SHAPE_J = (("--X-"), ("--X-"), ("-XX-"), ("----")), \ (("-X--"), ("-XXX"), ("----"), ("----")), \ (("-XX-"), ("-X--"), ("-X--"), ("----")), \ (("-XXX"), ("---X"), ("----"), ("----")) SHAPE_L = (("-X--"), ("-X--"), ("-XX-"), ("----")), \ (("XXX-"), ("X---"), ("----"), ("----")), \ (("-XX-"), ("--X-"), ("--X-"), ("----")), \ (("--X-"), ("XXX-"), ("----"), ("----")) SHAPE_O = (("-XX-"), ("-XX-"), ("----"), ("----")), \ (("-XX-"), ("-XX-"), ("----"), ("----")), \ (("-XX-"), ("-XX-"), ("----"), ("----")), \ (("-XX-"), ("-XX-"), ("----"), ("----")) SHAPE_S = (("----"), ("-XX-"), ("XX--"), ("----")), \ (("-X--"), ("-XX-"), ("--X-"), ("----")), \ (("----"), ("-XX-"), ("XX--"), ("----")), \ (("-X--"), ("-XX-"), ("--X-"), ("----")) SHAPE_T = (("XXX-"), ("-X--"), ("----"), ("----")), \ (("--X-"), ("-XX-"), ("--X-"), ("----")), \ (("-X--"), ("XXX-"), ("----"), ("----")), \ (("X---"), ("XX--"), ("X---"), ("----")) SHAPE_Z = (("----"), ("-XX-"), ("--XX"), ("----")), \ (("--X-"), ("-XX-"), ("-X--"), ("----")), \ (("----"), ("-XX-"), ("--XX"), ("----")), \ (("--X-"), ("-XX-"), ("-X--"), ("----")) # List of shapes SHAPES = (SHAPE_I, SHAPE_J, SHAPE_L, SHAPE_O, SHAPE_S, SHAPE_T, SHAPE_Z) # Shape Colors - Dictionary SHAPE_COLOR = {SHAPE_I: BLUE, SHAPE_J: PURPLE, SHAPE_L: RED, SHAPE_O: GREEN, SHAPE_S: WHITE, SHAPE_T: FUCHSIA, SHAPE_Z: AQUA} # Colors, web colors - dictionary COLOR_COLOR = {BLACK: 0, YELLOW: 1, BLUE: 2, PURPLE: 3, RED: 4, GREEN: 5, WHITE: 6, FUCHSIA: 7, AQUA: 8} # This is a reverse dictionary finder find_color = dict([[value, key] for key, value in COLOR_COLOR.items()]) # All possible combinations of rotations ROTATE_0_DEGREES = 0 ROTATE_90_DEGREES = 1 ROTATE_180_DEGREES = 2 ROTATE_270_DEGREES = 3 # This is the size of one shape, 4 x 4 SHAPE_SIZE = 4 class Shape(object): """A class for shapes""" def __init__(self, name, rotation, pos_y, pos_x): self.name = name # Name of the shape, e.g. SHAPE_T self.rotation = rotation # Rotation index of the shape self.pos_x = pos_x # X position of the shape, it can be from 1 to 10 self.pos_y = pos_y # Y position of the shape, it can be from 1 to 20 def drawShapeOnScreen(self): """ This method draws all boxes that form a shape on the screen and outline each box with a black edge line """ piece = self.name[self.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if piece[y][x] != EMPTY_BOX: # This line draws all the squares on the screen pygame.draw.rect(DISPLAY_SURFACE, SHAPE_COLOR[self.name], (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y, BOX_SIZE, BOX_SIZE)) # The following lines outline each square with 1 pixel black line # Top vertical pygame.draw.line(DISPLAY_SURFACE, BLACK, (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y), (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x + BOX_SIZE, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y), 1) # Right horizontal pygame.draw.line(DISPLAY_SURFACE, BLACK, (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x + BOX_SIZE, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y), (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x + BOX_SIZE, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y + BOX_SIZE), 1) # Left horizontal pygame.draw.line(DISPLAY_SURFACE, BLACK, (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y), (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y + BOX_SIZE), 1) # Bottom vertical pygame.draw.line(DISPLAY_SURFACE, BLACK, (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y + BOX_SIZE), (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x + BOX_SIZE, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y + BOX_SIZE), 1) def deleteShapeFromScreen(self): """ This method deletes the shape from the screen """ piece = self.name[self.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if piece[y][x] != EMPTY_BOX: # This line erases all the squares pygame.draw.rect(DISPLAY_SURFACE, BLACK, (x * BOX_SIZE + TOP_X + BOX_SIZE * self.pos_x, y * BOX_SIZE + TOP_Y + BOX_SIZE * self.pos_y, BOX_SIZE, BOX_SIZE)) def deleteShapeFromMatrix(self): """ This method deletes the shape from the matrix """ piece = self.name[self.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if x + self.pos_x < (MATRIX_WIDTH - 1) \ and y + self.pos_y < (MATRIX_HEIGHT -1) \ and piece[y][x] == FULL_BOX: MATRIX[y + self.pos_y][x + self.pos_x] = COLOR_COLOR[BLACK] resetMatrix() def returnMaxWidth(self): """ Each shape is 4 x 4, but the actual width is different for each shape, e.g. horizontal "I" shape is 4 boxes in width but the same vertical shape is 2 boxes in width This method returns the width from the left. See the shape definition above """ max_x = 0 piece = self.name[self.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if piece[x][y] == FULL_BOX and y > max_x : max_x = y return max_x def returnMinWidth(self): """ Each shape is 4 x 4, but the actual width is different for each shape, e.g. horizontal "I" shape is 4 boxes in width but the same vertical shape is 2 boxes in width This method returns the width from right. See the shape definition above """ min_x = SHAPE_SIZE piece = self.name[self.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if piece[x][y] == FULL_BOX and y < min_x : min_x = y return min_x def returnMaxHeight(self): """ Each shape is 4 x 4, but the actual height is different for each shape, e.g. horizontal "I" shape is 2 boxes in width but the same vertical shape is 4 boxes in height This method returns the height. See the shape definition above """ max_y = 0 piece = self.name[self.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if piece[x][y] == FULL_BOX and x > max_y : max_y = x return max_y def returnMaxHeightPerColumn(self, column): """ Each shape is 4 x 4, but the actual height is different per column for each shape, e.g. horizontal "T" shape can be 1 boxes or 3 boxes in height This method return the max height and that's the box that's the lowest in a shape """ max_y = 0 piece = self.name[self.rotation] for y in range(SHAPE_SIZE): if piece[y][column] == FULL_BOX and y > max_y: max_y = y return max_y def moveLeft(self): """ Move the shape left if possible """ if isAvailableLeft(self): self.deleteShapeFromMatrix() self.deleteShapeFromScreen() self.pos_x -= 1 updateShapeInMatrix(self) self.drawShapeOnScreen() def moveRight(self): """ Move the shape right if possible """ if isAvailableRight(self): self.deleteShapeFromMatrix() self.deleteShapeFromScreen() self.pos_x += 1 updateShapeInMatrix(self) self.drawShapeOnScreen() def moveDown(self): """ Move the shape down if possible """ if isAvailableDown(self): self.deleteShapeFromMatrix() self.deleteShapeFromScreen() self.pos_y += 1 updateShapeInMatrix(self) self.drawShapeOnScreen() def moveRotate(self): """ Rotate the shape if possible """ if isAvailableRotate(self): self.deleteShapeFromMatrix() self.deleteShapeFromScreen() self.rotation += 1 if self.rotation == 4: self.rotation = 0 updateShapeInMatrix(self) self.drawShapeOnScreen() def resetMatrix(): """ Updates the edges of the matrix in memory, that's the YELLOW borders """ for x in range(0, MATRIX_HEIGHT): MATRIX[x][0] = COLOR_COLOR[YELLOW] MATRIX[x][MATRIX_WIDTH - 1] = COLOR_COLOR[YELLOW] for y in range(0, MATRIX_WIDTH): MATRIX[0][y] = COLOR_COLOR[YELLOW] MATRIX[MATRIX_HEIGHT - 1][y] = COLOR_COLOR[YELLOW] def isAvailable(Shape): """ Is the shape available to be moved in the matrix Checks the new position initially when dropping it for the first time """ allowed = True piece = Shape.name[Shape.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if piece[y][x] == FULL_BOX and \ MATRIX[y + Shape.pos_y][x + Shape.pos_x] >= COLOR_COLOR[YELLOW]: allowed = False break return allowed def isAvailableLeft(Shape): """ Is the shape available to be moved left in the matrix """ allowed = True piece = Shape.name[Shape.rotation] for y in range(SHAPE_SIZE): if piece[y][Shape.returnMinWidth()] == FULL_BOX \ and MATRIX[y + Shape.pos_y][Shape.pos_x +Shape.returnMinWidth() - 1] >= COLOR_COLOR[YELLOW] : allowed = False break return allowed def isAvailableRight(Shape): """ Is the shape available to be moved right in the matrix """ allowed = True piece = Shape.name[Shape.rotation] for y in range(SHAPE_SIZE): if piece[y][Shape.returnMaxWidth()] == FULL_BOX \ and MATRIX[y + Shape.pos_y][Shape.pos_x + Shape.returnMaxWidth() + 1] >= COLOR_COLOR[YELLOW]: allowed = False break return allowed def isAvailableRotate(Shape): """ Is the shape available to be moved rotated in the matrix """ allowed = True oldMaxWidth= Shape.returnMaxWidth() oldMinWidth = Shape. returnMinWidth() oldMaxHeight = Shape.returnMaxHeight() oldRotation = Shape.rotation Shape.rotation += 1 if Shape.rotation == 4: Shape.rotation = 0 newMaxWidth = Shape.returnMaxWidth() newMinWidth = Shape.returnMinWidth() newMaxHeight = Shape.returnMaxHeight() if (newMinWidth - oldMinWidth) + Shape.pos_x < 0 \ or (newMaxWidth - oldMaxWidth) + Shape.pos_x >= (MATRIX_WIDTH - 2): allowed = False if (newMaxHeight - oldMaxHeight) + Shape.pos_y >= (MATRIX_HEIGHT -2): allowed = False Shape.rotation = oldRotation return allowed def isAvailableDown(Shape): """ Is the shape available to be moved down in the matrix """ allowed = True piece = Shape.name[Shape.rotation] for x in range(SHAPE_SIZE): if piece[Shape.returnMaxHeightPerColumn(x)][x] == FULL_BOX and \ MATRIX[Shape.pos_y + Shape.returnMaxHeightPerColumn(x) + 1][x + Shape.pos_x] >= COLOR_COLOR[YELLOW] : allowed = False break return allowed def updateShapeInMatrix(Shape): """ Updates the matrix with the moved shape """ piece = Shape.name[Shape.rotation] for x in range(SHAPE_SIZE): for y in range(SHAPE_SIZE): if piece[y][x] == "X" and (y + Shape.pos_y) in range (1, MATRIX_HEIGHT - 1) \ and (x + Shape.pos_x) in range(1, MATRIX_WIDTH - 1) \ and MATRIX[y + Shape.pos_y][x + Shape.pos_x] == COLOR_COLOR[BLACK]: MATRIX[y + Shape.pos_y][x + Shape.pos_x] = COLOR_COLOR[SHAPE_COLOR[Shape.name]] resetMatrix() def drawMatrixOnScreen(): """ Draws four bars where the game occurs """ # Left bar pygame.draw.rect(DISPLAY_SURFACE, YELLOW, (TOP_X, TOP_Y, BOX_SIZE, MATRIX_HEIGHT * BOX_SIZE + 1)) # Right bar pygame.draw.rect(DISPLAY_SURFACE, YELLOW, (TOP_X + (MATRIX_WIDTH - 1) * BOX_SIZE + 1, TOP_Y, BOX_SIZE, MATRIX_HEIGHT * BOX_SIZE + 1)) # Bottom bar pygame.draw.rect(DISPLAY_SURFACE, YELLOW, (TOP_X + BOX_SIZE, TOP_Y + (MATRIX_HEIGHT - 1) * BOX_SIZE + 1, (MATRIX_WIDTH - 2) * BOX_SIZE + 1, BOX_SIZE)) # Top bar pygame.draw.rect(DISPLAY_SURFACE, YELLOW, (TOP_X + BOX_SIZE, TOP_Y, (MATRIX_WIDTH - 2) * BOX_SIZE + 1, BOX_SIZE)) def checkFullLine(): """ Check if there is a full horizontal line in the matrix. """ for x in range(MATRIX_HEIGHT - 2, 0, - 1): if 0 not in MATRIX[x]: return x def printText(msg, FONT, x_cor, y_cor, b_color, f_color): """ Writes Text on the screen """ text = FONT.render(msg, True, b_color, f_color) textRect = text.get_rect() textRect.centerx = x_cor textRect.centery = y_cor DISPLAY_SURFACE.blit(text, textRect) def drawRectangle(x_cor, y_cor, width, height, color): """ Draws rectangle on the screen """ pygame.draw.rect(DISPLAY_SURFACE, color, (x_cor, y_cor, width, height)) def printScore(score): """ Prints score on the screen """ printText('SCORE:', FONT_SMALL, 490, 240, WHITE, BLACK) printText(str(score), FONT_SMALL, 580, 240, WHITE, BLACK) def shiftShapesOnScreen(row): """ Shift shapes down on the screen when one line is full and collapses """ for col in range(1, MATRIX_WIDTH -1): pygame.draw.rect(DISPLAY_SURFACE, find_color[MATRIX[row][col]], (TOP_X + BOX_SIZE * col, TOP_Y + BOX_SIZE * row, BOX_SIZE, BOX_SIZE)) # This line outlines each square with 1 pixel black line # Top vertical pygame.draw.line(DISPLAY_SURFACE, BLACK, (TOP_X + BOX_SIZE * col, TOP_Y + BOX_SIZE * row), (TOP_X + BOX_SIZE * col + BOX_SIZE, TOP_Y + BOX_SIZE * row), 1) # Right horizontal pygame.draw.line(DISPLAY_SURFACE, BLACK, (TOP_X + BOX_SIZE * col + BOX_SIZE, TOP_Y + BOX_SIZE * row), (TOP_X + BOX_SIZE * col + BOX_SIZE, TOP_Y + BOX_SIZE * row + BOX_SIZE), 1) # Left horizontal pygame.draw.line(DISPLAY_SURFACE, BLACK, (TOP_X + BOX_SIZE * col, TOP_Y + BOX_SIZE * row), (TOP_X + BOX_SIZE * col, TOP_Y + BOX_SIZE * row + BOX_SIZE), 1) # Bottom vertical pygame.draw.line(DISPLAY_SURFACE, BLACK, (TOP_X + BOX_SIZE * col, TOP_Y + BOX_SIZE * row + BOX_SIZE), (TOP_X + BOX_SIZE * col + BOX_SIZE, TOP_Y + BOX_SIZE * row + BOX_SIZE), 1) def shiftShapesInMatrix(row): """ Shift shapes down in the matrix when one line is full and collapses """ while row > 1: for xx in range(1, MATRIX_WIDTH): MATRIX[row][xx] = MATRIX[row - 1][xx] shiftShapesOnScreen(row) row -= 1 def main(): global FPS_CLOCK, DISPLAY_SURFACE, FONT_BIG, FONT_SMALL, FONT_SUPER_SMALL, SCORE pygame.init() FPS_CLOCK = pygame.time.Clock() DISPLAY_SURFACE = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption('KAtrix') FONT_BIG = pygame.font.SysFont(None, 44) FONT_SMALL = pygame.font.SysFont(None, 24) FONT_SUPER_SMALL = pygame.font.SysFont(None, 12) DISPLAY_SURFACE.fill(BLACK) resetMatrix() drawMatrixOnScreen() SCORE = 0 printScore(SCORE) printText('Program by: Kliment ANDREEV, 2015', FONT_SUPER_SMALL, 320, 470, SILVER, BLACK) # MAIN GAME LOOP while True: new_shape = Shape(SHAPES[random.randint(0, len(SHAPES) - 1)], ROTATE_0_DEGREES, 1, START_COL) NewPiece = True if isAvailable(new_shape): Shape.drawShapeOnScreen(new_shape) updateShapeInMatrix(new_shape) else: NewPiece=False Shape.drawShapeOnScreen(new_shape) drawRectangle(TOP_X, WINDOW_HEIGHT / 2 - 50, 241, 160, BLUE) printText("G A M E O V E R", FONT_BIG, WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2 + 30, YELLOW, BLUE) pygame.display.update() pygame.time.delay(3000) pygame.quit() sys.exit() while NewPiece: for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if (event.key == K_LEFT): Shape.moveLeft(new_shape) elif (event.key == K_RIGHT): Shape.moveRight(new_shape) elif (event.key == K_UP): Shape.moveRotate(new_shape) elif (event.key == K_DOWN): Shape.moveDown(new_shape) elif (event.key == K_SPACE): while isAvailableDown(new_shape): Shape.moveDown(new_shape) NewPiece = False while checkFullLine() > 0: SCORE += 10 printScore(SCORE) shiftShapesInMatrix(checkFullLine()) pygame.display.update() FPS_CLOCK.tick(FPS) if __name__ == '__main__': main()