tetris.py

Created by arthurjacquin

Created on July 10, 2021

4.97 KB


# Tetris - December 2020
# Arthur Jacquin (arthur@jacquin.xyz)
# https://github.com/arthur-jacquin/numworks-games

# Modules
from ion import keydown
from kandinsky import draw_string, fill_rect
from time import monotonic, sleep
from random import randint

# Tools
Xdom, Ydom = range(10), range(21)
commands = ('Move     ARROWS', 'Rotate   ()', 'Replay   OK', 'Quit     BACK')
TYPES = (
    (((-1, 1), (0, 1), (1, 0)), ((-1, -1), (-1, 0), (0, 1))), # Red (Z)
    (((-1, 0), (1, 0), (1, 1)), ((1, -1), (0, -1), (0, 1)), ((-1, -1), (-1, 0), (1, 0)), ((-1, 1), (0, 1), (0, -1))), # Orange (L)
    (((0, 1), (1, 0), (1, 1)),), # Yellow (O)
    (((-1, 0), (0, 1), (1, 1)), ((0, 1), (1, 0), (1, -1))), # Green (S)
    (((-1, 0), (1, 0), (2, 0)), ((0, -1), (0, 1), (0, 2))), # Light blue (I)
    (((-1, 0), (0, 1), (1, 0)), ((0, 1), (1, 0), (0, -1)), ((-1, 0), (0, -1), (1, 0)), ((0, -1), (-1, 0), (0, 1))), # Magenta (T)
    (((-1, 1), (-1, 0), (1, 0)), ((0, -1), (0, 1), (1, 1)), ((-1, 0), (1, 0), (1, -1)), ((-1, -1), (0, -1), (0, 1)))) # Dark blue (J)

# Colors
COL = (
    (255,)*3, # NOT BLOCK - Background
    (0,)*3, # NOT BLOCK - Text
    (238, 39, 51), # Red (Z)
    (248, 150, 34), # Orange (L)
    (253, 225, 0), # Yellow (O)
    (78, 183, 72), # Green (S)
    (43, 172, 226), # Light blue (I)
    (146, 43, 140), # Magenta (T)
    (0, 90, 157)) # Dark blue (J)

def sett(X, Y, TYPE):
    M[Y] = M[Y][:X] + str(TYPE) + M[Y][X+1:]
    if TYPE == 1: fill_rect(210+11*X, 211-11*Y, 9, 9, COL[int(tetro[2])+2])
    else: fill_rect(210+11*X, 211-11*Y, 9, 9, COL[TYPE])

def unpack(op = False):
    X, Y, TYPE, ORIENTATION = tetro
    if op == 'down': Y -= 1
    elif op == 'left': X -= 1
    elif op == 'right': X += 1
    elif op == 'rot_right': ORIENTATION = (ORIENTATION + 1)%len(TYPES[TYPE])
    elif op == 'rot_left': ORIENTATION = (ORIENTATION - 1)%len(TYPES[TYPE])
    cases = [[X, Y]]
    for case in TYPES[TYPE][ORIENTATION]: cases.append([X + case[0], Y + case[1]])
    return cases, [X, Y, TYPE, ORIENTATION]     

def form(op = False):
    if op:
        old, _ = unpack()
        new, newtetro = unpack(op)
    else: new, _ = unpack()
    for case in new:
        if not(case[0] in Xdom) or case[1] < 0 or int(M[case[1]][case[0]]) > 1: return False
    if op:
        for case in old: sett(case[0], case[1], 0)
    for case in new: sett(case[0], case[1], 1)
    return newtetro

def sample(): # 7-bag random generator
    indexes, res = list(range(7)), []
    for i in range(7):
        k = randint(0, 6-i)
        res.append(indexes.pop(k))
    return res

# Interface drawing
fill_rect(0, 0, 320, 222, COL[0])
fill_rect(207, 0, 1, 222, COL[1])
draw_string('Next', 133, 20, COL[1], COL[0])
draw_string('Lines', 28, 93, COL[1], COL[0])
for string in commands: draw_string(string, 28, 130 + 20*commands.index(string), COL[1], COL[0])
fill_rect(28, 20, 72, 24, COL[-1])
fill_rect(52, 44, 24, 24, COL[-1])
fill_rect(30, 22, 68, 20, COL[0])
fill_rect(54, 42, 20, 24, COL[0])
for i in range(6): draw_string('TETRIS'[i], 28+3+11*i, 20+4, COL[i+2], COL[0])
draw_string('NW', 54, 48, (100,)*3, COL[0])

while True:
    # Initialisation
    lines, M, bag = 0, ['0'*len(Xdom),]*len(Ydom), sample()
    time = monotonic()
    fill_rect(208, 0, 112, 222, COL[0])
    draw_string('0'+' '*7, 118, 93, COL[1], COL[0])

    # Main loop
    while True:
        if len(bag) < 2: bag += sample()
        last = tetro = [4, 19, bag[0], 0]
        bag.pop(0)
        if not(form(tetro)): break
        fill_rect(131, 42, 44, 22, COL[0])
        for case in [(0, 0)] + list(TYPES[bag[0]][0]): fill_rect(148+11*case[0]-5*(bag[0] in (2, 4)), 43 + 11*(1-case[1]), 9, 9, COL[bag[0]+2])
        while True: 
            # Time and movement gestion
            while monotonic() < time + 0.2 + .5/(lines+2):
                if keydown(2): break
                elif keydown(34): tetro = form('rot_right')
                elif keydown(33): tetro = form('rot_left')
                elif keydown(0): tetro = form('left')
                elif keydown(3): tetro = form('right')
                if tetro != last:
                    sleep(0.15)
                    if tetro: last = tetro
                    else: tetro = last
            time = monotonic()
            sleep(0.03)
            newtetro = form('down')
            if newtetro: last = tetro = newtetro
            else:
                for Y in Ydom:
                    for X in Xdom:
                        if M[Y][X] == '1': sett(X, Y, int(tetro[2])+2)
                completed = False
                for line in M[:]:
                    if not('0' in line): # Full row
                        M.remove(line)
                        M.append('0'*len(Xdom))
                        lines += 1
                        completed = True
                if completed: # Full refresh
                    sleep(0.1)
                    draw_string(str(lines), 118, 93, COL[1], COL[0])
                    for Y in Ydom:
                        for X in Xdom: sett(X, Y, int(M[Y][X]))
                break
    while not(keydown(4)): pass