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()