tetris.py

Created by laigna

Created on March 14, 2026

5.11 KB

Simple tetris


# Tetris - NumWorks
# Created by Alvar Laigna - https://alvarlaigna.com
# L/R:move Down:soft Up:hard OK/EXE:rotate Back:quit
from kandinsky import fill_rect as F,draw_string as D
from ion import keydown as K
from time import sleep as Z,monotonic as M
from random import randint
GW,GH=10,20;CS=11;OX=5;OY=1
BK=(0,)*3;GC=(25,)*3;BD=(50,)*3
# 7 tetrominoes: I O T S Z L J
PC=[((0,-1),(0,0),(0,1),(0,2)),
 ((0,0),(1,0),(0,1),(1,1)),
 ((-1,0),(0,0),(1,0),(0,-1)),
 ((0,0),(1,0),(-1,1),(0,1)),
 ((-1,0),(0,0),(0,1),(1,1)),
 ((0,-1),(0,0),(0,1),(1,1)),
 ((0,-1),(0,0),(0,1),(-1,1))]
CC=((0,200,200),(200,200,0),(160,0,200),
 (0,200,0),(220,30,30),(220,120,0),(30,30,220))
# Dimmed versions for ghost
GG=tuple(tuple(c//4 for c in col)for col in CC)
SX=OX+GW*CS+14

okv=1 if K(4)or K(52) else 0

def okd():
 return K(4)or K(52)

def okp():
 global okv
 d=okd()
 if d and not okv:
  okv=1
  return True
 if not d:okv=0
 return False

def wup():
 while okd():Z(0.02)
 Z(0.12)

def title():
 F(0,0,320,222,BK)
 D("T E T R I S",85,25,(0,200,200),BK)
 # Draw mini tetrominoes as decoration
 cols=[(220,30,30),(0,200,0),(200,200,0),(160,0,200)]
 for i,dx in enumerate([55,115,175,235]):
  F(dx,55,8,8,cols[i]);F(dx+9,55,8,8,cols[i])
  F(dx,64,8,8,cols[i]);F(dx+9,64,8,8,cols[i])
 D("Left / Right",85,95,(180,)*3,BK)
 D("move piece",100,113,(120,)*3,BK)
 D("Down = soft drop",70,138,(180,)*3,BK)
 D("Up = hard drop",78,156,(180,)*3,BK)
 D("OK = rotate",95,174,(180,)*3,BK)
 D("Press OK to start",70,205,(0,200,200),BK)
 while True:
  if okp():wup();return True
  if K(17):return False
  Z(0.05)

def tetris():
 gr=[[0]*GW for _ in range(GH)]
 sc=ln=lv=0
 def cl(x,y,c):
  if 0<=y<GH and 0<=x<GW:
   F(OX+x*CS,OY+y*CS,CS-1,CS-1,c)
 def fit(p,px,py):
  for dx,dy in p:
   x,y=px+dx,py+dy
   if x<0 or x>=GW or y>=GH:return False
   if y>=0 and gr[y][x]:return False
  return True
 def drp(p,px,py,c):
  for dx,dy in p:cl(px+dx,py+dy,c)
 def ghost_y(p,px,py):
  gy=py
  while fit(p,px,gy+1):gy+=1
  return gy
 def dgr():
  for y in range(GH):
   for x in range(GW):
    cl(x,y,CC[gr[y][x]-1]if gr[y][x]else BK)
 def dui():
  D("SCORE",SX,10,(120,)*3,BK)
  D(str(sc)+"      ",SX,26,(255,)*3,BK)
  D("LEVEL",SX,50,(120,)*3,BK)
  D(str(lv)+"  ",SX,66,(0,200,200),BK)
  D("LINES",SX,90,(120,)*3,BK)
  D(str(ln)+"   ",SX,106,(200,200,0),BK)
 def dnx(ci):
  D("NEXT",SX,132,(120,)*3,BK)
  F(SX,150,48,48,(15,)*3)
  F(SX+1,151,46,46,BK)
  for dx,dy in PC[ci]:
   F(SX+15+dx*10,170+dy*10,9,9,CC[ci])
 def lflash(rows):
  for _ in range(3):
   for y in rows:
    for x in range(GW):cl(x,y,(255,)*3)
   Z(0.06)
   for y in rows:
    for x in range(GW):cl(x,y,BK)
   Z(0.06)
 # Init screen
 F(0,0,320,222,BK)
 # Grid border
 F(OX-2,OY-2,GW*CS+4,GH*CS+4,BD)
 F(OX-1,OY-1,GW*CS+2,GH*CS+2,GC)
 F(OX,OY,GW*CS,GH*CS,BK)
 dgr();dui()
 ci=randint(0,6);ni=randint(0,6)
 p=list(PC[ci]);px,py=GW//2,1
 dnx(ni)
 # Draw ghost + piece
 gy=ghost_y(p,px,py)
 if gy!=py:drp(p,px,gy,GG[ci])
 drp(p,px,py,CC[ci])
 tm=M();lt=M()
 das=0;das_dir=0;das_t=0
 while True:
  if K(17):return False
  sp=max(0.05,0.5-lv*0.04)
  now=M()
  moved=False
  # DAS: hold L/R for auto-repeat
  lr=(-1 if K(0)else 1 if K(3)else 0)
  if lr!=0:
   go=False
   if lr!=das_dir:
    das_dir=lr;das_t=now;go=True
   elif now-das_t>0.18:
    go=True;das_t=now-0.12
   if go and fit(p,px+lr,py):
    drp(p,px,gy if gy!=py else py,BK)
    if gy!=py:drp(p,px,gy,BK)
    drp(p,px,py,BK);px+=lr;moved=True
  else:das_dir=0
  if K(2): # soft drop
   if fit(p,px,py+1):
    drp(p,px,gy if gy!=py else py,BK)
    if gy!=py:drp(p,px,gy,BK)
    drp(p,px,py,BK);py+=1;moved=True;tm=now
   Z(0.04)
  elif K(1): # hard drop
   drp(p,px,py,BK)
   if gy!=py:drp(p,px,gy,BK)
   py=ghost_y(p,px,py);drp(p,px,py,CC[ci])
   tm=now-sp
   Z(0.05)
  elif okp(): # rotate
   nr=[(-dy,dx)for dx,dy in p]
   for ox in(0,1,-1,2,-2):
    if fit(nr,px+ox,py):
     drp(p,px,py,BK)
     if gy!=py:drp(p,px,gy,BK)
     p=nr;px+=ox;moved=True;break
   Z(0.14)
  # Redraw ghost + piece if moved
  if moved:
   gy=ghost_y(p,px,py)
   if gy!=py:drp(p,px,gy,GG[ci])
   drp(p,px,py,CC[ci])
  # Gravity
  if now-tm>=sp:
   tm=now
   if fit(p,px,py+1):
    drp(p,px,py,BK)
    if gy!=py:drp(p,px,gy,BK)
    py+=1;gy=ghost_y(p,px,py)
    if gy!=py:drp(p,px,gy,GG[ci])
    drp(p,px,py,CC[ci])
   else:
    # Lock piece
    for dx,dy in p:
     x,y=px+dx,py+dy
     if 0<=y<GH:gr[y][x]=ci+1
    # Line clear
    full=[y for y in range(GH)if all(gr[y])]
    if full:
     lflash(full)
     n=len(full)
     sc+=(0,100,300,500,800)[n]*(lv+1)
     ln+=n;lv=ln//10
     for y in sorted(full,reverse=True):
      gr.pop(y);gr.insert(0,[0]*GW)
     dgr()
    dui()
    # Spawn
    ci=ni;ni=randint(0,6)
    p=list(PC[ci]);px,py=GW//2,1
    dnx(ni)
    if not fit(p,px,py):
     # Game over
     F(OX,OY+75,GW*CS,55,BK)
     F(OX+2,OY+77,GW*CS-4,51,(20,)*3)
     D("GAME OVER",OX+14,OY+80,(255,50,50),(20,)*3)
     D(str(sc)+" pts",OX+20,OY+100,(255,)*3,(20,)*3)
     D("OK=again Back=quit",2,210,(120,)*3,BK)
     while True:
      if K(17):return False
      if okp():wup();return True
      Z(0.05)
    gy=ghost_y(p,px,py)
    if gy!=py:drp(p,px,gy,GG[ci])
    drp(p,px,py,CC[ci])
  Z(0.025)

# Main loop
if title():
 while tetris():pass

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.