tetris.py

Created by valmontechno

Created on December 19, 2023

10.1 KB


from math import *
from kandinsky import *
from ion import *
from random import *
from time import monotonic, sleep

WIDTH = 320
HEIGHT = 222

gridWidth = 10
gridHeight = 20
squareSize = HEIGHT // gridHeight
gridOffset = (WIDTH - gridWidth * squareSize) // 2
borderSize = 1
startBlockX = gridWidth // 2
dangerDistance = gridHeight // 2.5
statOffset = gridOffset + (gridWidth + 2) * squareSize

linePoint = [100, 300, 500, 800]
comboPoint = 50

palette = {' ':'#b0b0b0', 'I':'#007ace', 'O':'#efaa00', 'T':'#660066', 'L':'#b45700', 'J':'#000073', 'Z':'#990000', 'S':'#009700'}
bgColor = '#141414'
dangerColor = '#683b3b'
sparkColor = '#ffffff'
deadBlockColor = '#303030'
textColor = '#eeeeee'
frameColors = ('#363536', '#454545')

tetromino = {
    'I':(
        ((-2, 0), (-1, 0), (0, 0), (1, 0)),
        ((0, -1), (0, 0), (0, 1), (0, 2)),
        ((-2, 1), (-1, 1), (0, 1), (1, 1)),
        ((-1, -1), (-1, 0), (-1, 1), (-1, 2))
    ),
    'O':(
        ((0, 0), (0, 1), (-1, 0), (-1, 1)),
    ),
    'T':(
        ((-1, 0), (0, 0), (1, 0), (0, 1)),
        ((-1, 0), (0, 0), (0, -1), (0, 1)),
        ((-1, 0), (0, 0), (0, -1), (1, 0)),
        ((0, 1), (0, 0), (0, -1), (1, 0))
    ),
    'L':(
        ((-1, 1), (-1, 0), (0, 0), (1, 0)),
        ((-1, -1), (0, -1), (0, 0), (0, 1)),
        ((-1, 0), (0, 0), (1, 0), (1, -1)),
        ((0, -1), (0, 0), (0, 1), (1, 1))
    ),
    'J':(
        ((-1, 0), (0, 0), (1, 0), (1, 1)),
        ((0, -1), (0, 0), (0, 1), (-1, 1)),
        ((-1, -1), (-1, 0), (0, 0), (1, 0)),
        ((1, -1), (0, -1), (0, 0), (0, 1))
    ),
    'Z':(
        ((-1, -1), (0, -1), (0, 0), (1, 0)),
        ((1, -1), (1, 0), (0, 0), (0, 1)),
        ((-1, 0), (0, 0), (0, 1), (1, 1)),
        ((0, -1), (0, 0), (-1, 0), (-1, 1))
    ),
    'S':(
        ((-1, 0), (0, 0), (0, -1), (1, -1)),
        ((0, -1), (0, 0), (1, 0), (1, 1)),
        ((-1, 1), (0, 1), (0, 0), (1, 0)),
        ((-1, -1), (-1, 0), (0, 0), (0, 1))
    )
}

def createArray(w, h, e):
    return [[e]*w for _ in range(h)]

def copyArray(array):
    newArray = []
    for i in array:
        newArray.append(i.copy())
    return newArray

def notOverlap(block, xOffset, yOffset):
    for square in block:
        x = square[0] + xOffset
        y = square[1] + yOffset
        if not (x >=0 and x < gridWidth and y >= 0 and y < gridHeight and grid[y][x] == ' '):
            return False
    return True

def findNewBlock():
    global blockQueue, blockDiscard, blockShape
    if keydown(KEY_IMAGINARY):
        blockShape = 'I'
    else:
        if len(blockQueue) < 2:
            for square in blockDiscard:
                blockQueue.append(square)
            blockDiscard = []
        blockShape = blockQueue.pop(0)
        blockDiscard.insert(randint(0, 0), blockShape)
    drawNextBlock()

def generateNewBlock():
    global blockQueue, blockShape, blockX, blockY, blockRotation, game, startTimeBlockFall
    findNewBlock()
    blockX = startBlockX
    blockY = - min(tetromino[blockShape][0], key=lambda x: x[1])[1]
    blockRotation = 0
    if not notOverlap(tetromino[blockShape][blockRotation], blockX, blockY):
        game = False
    startTimeBlockFall = monotonic()

def moveBlock(direction):
    global timeMove, blockX
    timeMove = monotonic() + 0.1
    if notOverlap(tetromino[blockShape][blockRotation], blockX + direction, blockY):
        blockX += direction

def rotateBlock():
    global blockRotation
    newRotation = blockRotation + 1
    if newRotation == len(tetromino[blockShape]):
        newRotation = 0
    if notOverlap(tetromino[blockShape][newRotation], blockX, blockY):
        blockRotation = newRotation

def blockFall():
    global blockX, blockY, danger
    if notOverlap(tetromino[blockShape][blockRotation], blockX, blockY + 1):
        blockY += 1
    else:
        for square in tetromino[blockShape][blockRotation]:
            grid[square[1] + blockY][square[0] + blockX] = blockShape
        checkLine()
        danger = grid.count([' '] * gridWidth) <= dangerDistance
        generateNewBlock()

def checkLine():
    global grid, displayGrid, score, level, totalLines, fallDelay, combo
    deletedLine = []
    for i in range(gridHeight):
        if not ' ' in grid[i]:
            deletedLine.append(i)
    linesCount = len(deletedLine)
    if linesCount:
        for i in reversed(deletedLine):
            grid.pop(i)
        for i in range(linesCount):
            grid.insert(0, [' '] * gridWidth)
        for i in range(linesCount):
            for c in (sparkColor, palette[' ']):
                for y in deletedLine:
                    for x in range(gridWidth):
                        fill_rect(x * squareSize + borderSize + gridOffset, (y * squareSize) + borderSize, squareSize - borderSize, squareSize - borderSize, c)
                sleep(0.1)
        combo += 1
        score += linePoint[linesCount -1] * (level +1) + comboPoint * combo * level
        totalLines += linesCount
        level = totalLines // 10
        fallDelay = calculateFallDelay(level)
        updateInterface()
        displayGrid = copyArray(grid)
    else:
        combo = -1
        fill_rect(WIDTH - 85, 130, 70, 40, bgColor)

def calculateFallDelay(level):
    level = min(level, 15)
    return (-0.007 * level + 0.807) ** (level -1)

def drawInterface():
    fill_rect(0, 0, WIDTH, HEIGHT, bgColor)
    drawFrame(10, 20, 80, 40)
    drawFrame(20, 9, 60, 20)
    draw_string('Score', 25, 10, textColor, frameColors[0])
    drawFrame(15, 80, 70, 40)
    draw_string('Level', 25, 82, textColor, frameColors[0])
    drawFrame(15, 130, 70, 40)
    draw_string('Lines', 25, 131, textColor, frameColors[0])
    drawFrame(WIDTH - 90, 20, 80, 60)
    drawFrame(WIDTH - 80, 9, 60, 20)
    draw_string('Next', WIDTH - 70, 10, textColor, frameColors[0])
    updateInterface()

def updateInterface():
    draw_string(str(score), 20, 35, textColor, frameColors[0])
    draw_string(str(level), 25, 100, textColor, frameColors[0])
    draw_string(str(totalLines), 25, 150, textColor, frameColors[0])
    if combo > 0:
        drawFrame(WIDTH - 85, 130, 70, 40)
        draw_string('Combo', WIDTH - 75, 131, textColor, frameColors[0])
        draw_string(str(combo), WIDTH - 75, 150, textColor, frameColors[0])

def drawFrame(x, y, width, height):
    fill_rect(x, y, width, height, frameColors[1])
    fill_rect(x + borderSize, y + borderSize, width - 2 * borderSize, height -  2 * borderSize, frameColors[0])

def drawSquare(x, y, color):
    fill_rect(x * squareSize + borderSize + gridOffset, y * squareSize + borderSize, squareSize - borderSize, squareSize - borderSize, color)

def drawNextBlock():
    fill_rect(WIDTH - 80, 30, 60, 40, frameColors[0])
    nextBlock = tetromino[blockQueue[0]][0]
    squaresX = [i[0] for i in nextBlock]
    squareMinX = min(squaresX)
    blockSquareWidth = max(squaresX) - squareMinX + 1
    squaresY = [i[1] for i in nextBlock]
    squareMinY = min(squaresY)
    blockSquareHeight = max(squaresY) - squareMinY + 1
    for square in nextBlock:
        x, y = square
        fill_rect(int((x - squareMinX - blockSquareWidth / 2) * squareSize + borderSize + WIDTH - 50), int((y - squareMinY - blockSquareHeight / 2) * squareSize + borderSize + 50), squareSize - borderSize, squareSize - borderSize, palette[blockQueue[0]])

def drawGrid():
    for y in range(gridHeight):
        for x in range(gridWidth):
            if displayGrid[y][x] == ' ':
                if danger:
                    drawSquare(x, y, dangerColor)
                else:
                    drawSquare(x, y, palette[' '])
            else:
                drawSquare(x, y, palette[displayGrid[y][x]])

def gameOver():
    global game
    game = False
    for y in reversed(range(gridHeight)):
        for x in range(gridWidth):
            fill_rect(x * squareSize + borderSize + gridOffset, (y * squareSize) + borderSize, squareSize - borderSize, squareSize - borderSize, deadBlockColor)
        sleep(0.05)
    drawFrame(110, 95, 100, 30)
    draw_string('Game Over', 115, 100, dangerColor, frameColors[0])
    while keydown(KEY_OK) or keydown(KEY_EXE):
        pass
    while not (keydown(KEY_OK) or keydown(KEY_EXE)):
        pass

def mainMenu():
    fill_rect(0, 0, WIDTH, HEIGHT, bgColor)
    drawFrame(30, 55, 250, 80)
    draw_string('Press OK', 120, 160, textColor, bgColor)
    title = (
        'ZZZ OOO TTT LL  I  SS',
        ' Z  O    T  L L I S  ',
        ' Z  OO   T  LL  I  S ',
        ' Z  O    T  L L I   S',
        ' Z  OOO  T  L L I SS '
        )
    for y in range(len(title)):
        for x in range(len(title[0])):
            if title[y][x] != ' ':
                drawSquare(x - 6, y + 6, palette[title[y][x]])
    while keydown(KEY_OK) or keydown(KEY_EXE):
        pass
    while not (keydown(KEY_OK) or keydown(KEY_EXE)):
        pass


while True:

    mainMenu()
    
    grid = createArray(gridWidth, gridHeight, ' ')
    displayGrid = createArray(gridWidth, gridHeight, ' ')

    game = True
    score = 0
    level = 0
    totalLines = 0
    combo = -1
    fallDelay = calculateFallDelay(0)

    blockQueue = []
    for i in tetromino.keys():
        blockQueue.insert(randint(0, len(list(tetromino.keys()))), i)
    blockShape = ' '
    blockDiscard = []

    drawInterface()

    blockX = 0
    blockY = 0
    blockRotation = 0
    generateNewBlock()

    danger = False

    startTimeBlockFall = monotonic()
    timeMove = 0
    rotationKeyUp = False


    while game:

        displayGrid = copyArray(grid)

        if  monotonic() >= timeMove:
            if keydown(KEY_LEFT):
                moveBlock(-1)
            if keydown(KEY_RIGHT):
                moveBlock(1)
            if keydown(KEY_DOWN):
                timeMove = monotonic() + 0.1
                blockFall()

        if rotationKeyUp:
            if keydown(KEY_OK) or keydown(KEY_EXE):
                rotationKeyUp = False
                rotateBlock()
        elif not (keydown(KEY_OK) or keydown(KEY_EXE)):
            rotationKeyUp = True

        if monotonic() >= startTimeBlockFall + fallDelay:
            startTimeBlockFall = monotonic()
            blockFall()

        for square in tetromino[blockShape][blockRotation]:
            displayGrid[square[1] + blockY][square[0] + blockX] = blockShape
        
        drawGrid()

    gameOver()
    

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.