numtris_v05.py

Created by fime

Created on September 18, 2023

12.7 KB

Multi-mode tetris clone object-oriented, suporting versus. I think this wont work on the regular Epsilon, so please try it with Omega/Upsilon/Khi/etc. Needs some polishing that I won’t do….. everyone is free to continue this project with credit to me


from kandinsky import fill_rect,draw_string as dTxt
from time import monotonic as cTime,sleep
from random import randint as rInt,choice
from ion import keydown as key
from math import *
def rect(x,y,w,h,c):fill_rect(int(x),int(y),int(w),int(h),c)

TITLE=" NUMTRIS "
W,H=320,222
VOID,OUT,BLK,A,B,C,D,E,F,G=[c for c in "-*=ABCDEFG"]
PLAY,LINE_ANIM,DEATH,END=0,1,2,3
SOLO_A,SOLO_B,VS_A,VS_B=0,1,2,3
WON,LOST=0,1
TETRA="ITLJZSO"
TETRA_GRID=((8738, 4),(184, 3),(60, 3),(57, 3),(51, 3),(30, 3),(15, 2))
TETRA_COL=(A,B,C,D,E,F,G)

P1_KEYSET,P2_KEYSET=(0,3,1,2,12,13),(38,40,33,45,32,34)
def read_col(hexrvb):return [int("0x"+hexrvb[i:i+2]) for i in range(0,6,2)]

DARK,BRIGHT=(10,10,10),(255,255,255)
FG,BG=(255,255,255),(30,30,30)
BD,SL=(100,100,100),(200,0,0)
P1_COL,P2_COL='40a0f0','c86464'
CUBE_COL="""606060
ff2000
fff000
00ff30
00e0a0
0080ff
ff00e0
ffa000"""

def draw_title(x,y,j):
  rect(x,y,len(TITLE)*10,22,BG)
  c,j=SL,j%len(TITLE)
  for i,l in enumerate(TITLE):dTxt(l,x,y+(j!=i)*2,BRIGHT,c);x+=10
def fade_col(c1,c2,f):return tuple((1-f)*v1+f*v2 for v1,v2 in zip(c1,c2))
def draw_bg():
  rect(0,0,W,H,BG)
  tetras,c=[],BD
  for t in TETRA:tetras+=[load_tetra(t)]
  for i in range(W//20*H//20):
    draw_tetra(tetras[i%len(tetras)],10+i%(W//20)*20,10+i//(W//20)*20,2,c)
def draw_cube(x,y,s,c,typ=None):
  u=1
  rect(x,y,s,s,fade_col(c,DARK,0.5))
  rect(x+u,y+u,s-2*u,s-2*u,c)
def draw_void(x,y,w,h,c):
  rect(x,y,w,h,c)
  c=fade_col(c,BRIGHT,0.2)
  rect(x+w//2,y+h//2,1,1,c)
def draw_tetra(tetra,x,y,s,col):
  w=tetra[1]
  for i,c in enumerate(tetra[0]):
    if int(c):draw_cube(x+i%w*s,y+i//w*s,s,col)
def load_tetra(symb):
  i=TETRA.index(symb)
  r,w=TETRA_GRID[i]
  s=bin(r)[2:]
  return ["0"*(w**2-len(s))+s,w,TETRA_COL[i],[0,0]]
def str_set_char(string,i,char):
  return string[:i]+char+string[i+1:]
def sec(t):return cTime()-t
def transform_tetra(ttr,x=0,y=0,rot=0):
  n_ttr=ttr[:3]+[ttr[3][:]]
  n_ttr[3][0]+=x
  n_ttr[3][1]+=y
  rot%=4
  while rot>0:
    n_ttr[0],w,grid="",ttr[1],n_ttr[0]
    rot-=1
    for i in range(w**2):
      n_ttr[0]+=grid[(w-i-1)%w*w+i//w%w]
  return n_ttr
def draw_frame(x,y,w,h,c1,s=2):
  c2=fade_col(c1,(0,0,0),0.5)
  rect(x-2*s-1,y-2*s-1,w+4*s+2,h+4*s+2,c2)
  rect(x-2*s,y-2*s,w+4*s,h+4*s,c2)
  rect(x-s,y-2*s,w+3*s,h+3*s,c1)
  rect(x,y-2,w+2,h+2,c2)
def shuffle(lst):
  new=[]
  while len(new)<len(lst):
    c=choice(lst)
    if not c in new:new+=[c]
  return new
def open_color_set(set):
  return [read_col(hx) for hx in set.split("\n")]

class TetrisGame():
  games_cnt=0
  def __init__(
it,x=0,y=0,
w=10,h=20,
s=10,
col="FF0000",
bg_col="202020",
p=None,
lvl=0):
    TetrisGame.games_cnt+=1
    it.id=it.games_cnt+0
    it.grid=VOID*w*h
    it.x,it.y,it.w,it.h,it.s=x,y,w,h,s
    it.inputs=[]
    it.anim_stat=-1
    it.spd,it.ln_cnt,it.lvl,it.pnt=1,0,lvl,0
    it.bg_col,it.ln_col=read_col(bg_col),read_col(col)
    it.colors=open_color_set(CUBE_COL)
    it.ttr_lst=shuffle(TETRA)
    it.tetra=["",0,VOID,[0,0]]
    it.status,it.paused=PLAY,0
    it.blk_theshold,it.move_theshold=0.3,0.3
    it.tmrs={"fall":cTime(),"blkd":0,"move":-1,"rot":0,"drop":0,"anim":0}
    it.update_panel=p
    it.del_lines=[]
    draw_frame(it.x,it.y,it.w*it.s,it.h*it.s,it.ln_col)
    it.disp_grid()
    it.next_tetra(addPnt=0)
    
  def get_col_set(it,symb):
    return it.colors[(BLK,A,B,C,D,E,F,G).index(symb)]
      
  def process(it):
    if it.paused:return
    if it.status==PLAY:
      n_ttr=transform_tetra(it.tetra,y=1)
      if not (it.hit_box(n_ttr)):
        it.tmrs["blkd"]=0
        if sec(it.tmrs["fall"])>it.spd:
          it.tmrs["fall"]=cTime()
          it.disp_tetra(hide=1)
          it.tetra=n_ttr
          it.disp_tetra()
      elif it.tmrs["blkd"]==0:
        it.tmrs["blkd"]=cTime()
      elif it.tmrs["blkd"]>0 and sec(it.tmrs["blkd"])>it.blk_theshold:
        it.tmrs["blkd"]=0
        it.next_tetra()
      inp=it.inputs
      move=("R" in inp)-("L" in inp)
      if move:
        if it.tmrs["move"]<0:it.tmrs["move"]=cTime()
        elif cTime()-it.tmrs["move"]<it.move_theshold:move=0
        else:it.tmrs["move"]=0
      else:it.tmrs["move"]=-1
      fall,rot="D" in inp,(">" in inp)-("<" in inp)
      if rot:
        if not it.tmrs["rot"]:it.tmrs["rot"]=1
        else:rot=0
      else:it.tmrs["rot"]=0
      n_ttr=transform_tetra(it.tetra,x=move,y=fall,rot=rot)
      if not it.hit_box(n_ttr) and (move or fall or rot):
        it.disp_tetra(hide=1)
        it.pnt+=fall
        it.tetra=n_ttr
        it.disp_tetra()
      if "U" in inp and not it.tmrs["drop"] and not it.tmrs["blkd"]:
        it.disp_tetra(hide=1)
        it.tmrs["drop"]=1
        n_ttr=it.tetra
        c=0
        while not it.hit_box(n_ttr):
          c+=1
          n_ttr=transform_tetra(n_ttr,y=1)
        if c>0:
          it.tetra[3][1]=n_ttr[3][1]-1
          it.disp_tetra()
          it.pnt+=20
      elif not "U" in inp:it.tmrs["drop"]=0
    elif it.status==LINE_ANIM:
      if sec(it.tmrs["anim"])>0.1:it.tmrs["anim"]=cTime()
      else:return
      if it.anim_stat<0:it.anim_stat=0
      it.anim_stat+=1
      for l in it.detect_line():
        if it.anim_stat%2:draw_void(it.x,it.y+l*it.s,it.w*it.s,it.s,it.bg_col)
        else:
          for x in range(it.w):it.disp_case(x,l)
      if it.anim_stat==5:
        for l in it.detect_line():it.pop_line(l)
        it.disp_grid()
        it.disp_tetra()
        it.status=PLAY
        it.tmrs["fall"]=cTime()
        it.anim_stat=-1
    elif it.status==DEATH:
      if sec(it.tmrs["anim"])>0.1:it.tmrs["anim"]=cTime()
      else:return
      if it.anim_stat<0:it.anim_stat=0
      y=it.anim_stat
      it.set_line(y,BLK*it.w)
      for x in range(it.w):
        it.disp_case(x,y)
      if it.anim_stat==it.h-1:it.status=END
      it.anim_stat+=1
    it.inputs=[]
      
  def next_tetra(it,addPnt=1):
    it.ttr_lst.pop(0)
    it.ttr_lst+=[choice(TETRA)]
    it.tetra_2grid()
    it.tetra=load_tetra(it.ttr_lst[0])
    lines=len(it.detect_line())
    if addPnt:it.pnt+=(20,100,200,500,1000)[min(lines,4)]
    if lines!=0:
      it.status=LINE_ANIM
      it.lvl+=(it.ln_cnt%10+lines)//10
      it.ln_cnt+=lines
    else:
      it.tetra[3]=[it.w//2-2,0]
      if it.hit_box(it.tetra):it.status=DEATH
      it.disp_tetra()
    it.spd=1-(it.lvl/3.5/sqrt(it.lvl+1))
    try:it.update_panel()
    except:pass
        
  def pause(it):
    if it.paused==1:
      it.paused=0
      it.disp_grid()
      it.tmrs["fall"]=cTime()-it.tmrs["fall"]
      if it.status==PLAY:it.disp_tetra()
      return
    x,y,w,h=it.x,it.y,it.w*it.s,it.h*it.s
    it.paused=1
    it.tmrs["fall"]=cTime()-it.tmrs["fall"]
    rect(x,y,w,h,it.bg_col)
    it.disp_msg("PAUSED")
    
  def disp_msg(it,txt):
    x,y,w,h=it.x,it.y,it.w*it.s,it.h*it.s
    rect(x,it.y+h//2-10,w,20,it.ln_col)
    dTxt(txt,x+w//2-5*len(txt),y+h//2-10,BG,it.ln_col)

  def disp_grid(it):
    for x in range(it.w):
      for y in range(it.h):
        it.disp_case(x,y)

  def hit_box(it,tetra):
    scan=""
    w,symb,pos=tetra[1:]
    for i,c in enumerate(tetra[0]):
      if int(c):scan+=it.get_case(pos[0]+i%w,pos[1]+i//w)
    return scan!=4*VOID
  
  def randomize_grid(it,diff,start):
    for l in range(start,it.h):
      while not (0<it.get_line(l).count(VOID)<it.w):
        txt=""
        for i in range(it.w):txt+=choice((BLK,)*6+(VOID,)*(diff//2+2))
        it.set_line(l,txt)
    it.disp_grid()
    it.disp_tetra()

  def disp_tetra(it,hide=0):
    x,y=it.tetra[3]
    if hide:
      w=it.tetra[1]
      for i,c in enumerate(it.tetra[0]):
        if int(c):it.disp_case(i%w+x,i//w+y)
      return
    symb=it.tetra[2]
    draw_tetra(it.tetra,it.x+x*it.s,it.y+y*it.s,it.s,it.get_col_set(symb))
    
  def disp_case(it,xin,yin):
    ch,x,y,s=it.grid[it.w*yin+xin],it.x+(xin*it.s),it.y+(yin*it.s),it.s
    if ch==VOID:
      draw_void(x,y,s,s,it.bg_col)
      return
    draw_cube(x,y,s,it.get_col_set(ch))
  
  def get_case(it,x,y):
    if 0<=x<it.w and 0<=y<it.h:return it.grid[y*it.w+x]
    else:return OUT

  def set_line(it,id,line):
    it.grid=it.grid[:it.w*id]+line+it.grid[it.w*(id+1):]

  def get_line(it,id):
    return it.grid[it.w*id:it.w*(id+1)]

  def pop_line(it,id):
    it.del_lines+=[id]
    prev_grid=it.grid[:]
    for y in range(1,id+1):
      it.set_line(y,prev_grid[(y-1)*it.w:y*it.w])
    it.set_line(0,VOID*it.w)

  def detect_line(it):
    lines_id=[]
    for i in range(it.h):
      if not VOID in it.get_line(i):
        lines_id+=[i]
    return lines_id
  
  def tetra_2grid(it):
    ctt,w,c,pos=it.tetra
    for i,v in enumerate(ctt):
      if int(v):
        it.grid=str_set_char(it.grid,pos[0]+pos[1]*it.w+i%w+i//w*it.w,c)
            
def init_panel(x,y,w,h,c,games,mode):
  rect(x,y,w,h,BG)
  for i,game in enumerate(games):
    x2,y2=x+w//2+(-16,(-38,6)[i])[mode>SOLO_B],y+h-40
    draw_frame(x2,y2,32,32,game.ln_col,1)
  txt="SCORE"
  dTxt(txt,x+w//2-len(txt)*4,y+5,c,BG,1)
  txt="LINES  LVL"
  dTxt(txt,x+w//2-len(txt)*4,y+h//2-30,c,BG,1)
  txt="NEXT"
  dTxt(txt,x+w//2-len(txt)*4,y+h-60,c,BG,1)

def update_panel(x,y,w,h,c,games,mode):
  for i,game in enumerate(games):
    txt=str(game.pnt)
    dTxt(txt,x+w//2-len(txt)*5,y+20+20*i,game.ln_col,BG)
    txt="%i "%(game.ln_cnt)
    dTxt(txt,x+w//2-len(txt)*10,y+h//2-10+20*i,game.ln_col,BG)
    txt=" %i"%(game.lvl)
    dTxt(txt,x+w//2,y+h//2-10+20*i,game.ln_col,BG)
    symb=game.ttr_lst[1]
    ttr_id=TETRA.index(symb)
    x2,y2=x+w//2+(-16,(-38,6)[i])[mode>SOLO_B],y+h-40
    rect(x2,y2,32,32,BG)
    draw_tetra(load_tetra(symb),x2+(4-TETRA_GRID[ttr_id][1])*4,y2+(4-TETRA_GRID[ttr_id][1])*4,8,game.get_col_set(TETRA_COL[ttr_id]))
  rect(x+w//2,y+h//2-30,1,60,c)

def play_game(mode=SOLO_A,diff=0,w=10,h=20):
  draw_bg()
  vs=mode>1
  s=(10,8)[vs]
  offset=(W-(w*s)*(1+vs)-90)//(3+vs)
  panel_prop=(offset*2+s*w,10,90,200,(150,150,150))
  def disp_panel():update_panel(*panel_prop+(games,mode))
  games=[TetrisGame(x=offset,y=10,w=w,h=h,col=P1_COL,s=s,p=disp_panel,lvl=diff)]
  if vs:games+=[TetrisGame(x=w*s+90+3*offset,y=10,w=w,h=h,col=P2_COL,s=s,p=disp_panel,lvl=diff)]
  keysets=(P1_KEYSET,P2_KEYSET)[:vs+1]
  pause_btn=0
  lines_to_del=h//2+h//3-diff//2
  if mode in (SOLO_B,VS_B):
    for game in games:game.randomize_grid(diff,lines_to_del)
  end=()

  draw_frame(*panel_prop)
  init_panel(*panel_prop+(games,mode))
  disp_panel()
  while end==():
    if key(17):
      if not pause_btn:
        for game in games:game.pause()
      pause_btn=1
    else:pause_btn=0
    for game,keyset in zip(games,keysets):
      for i,k in enumerate(keyset):
        if key(k):game.inputs+=["LRUD<>"[i]]
      game.process()
      if game.status==END:
        end=(LOST,game.id)
      if mode in (SOLO_B,VS_B) and game.status==PLAY:
        if not BLK in game.grid:end=(WON,game.id)
  for game in games:
    w=(end[0]==WON)==(end[1]==game.id)
    game.disp_msg(("LOST","WON")[w])

class Elmnt:
  H,TYPE=0,None
  def __init__(it,name):it.name=name
class Sld(Elmnt):
  H,TYPE=44,"sld"
  def __init__(it,name,content):
    super().__init__(name)
    it.content,it.selec=content,0
  def draw(it,x,y,hilight=0):
    dTxt(it.name,x,y,FG,BG)
    y+=20
    for i,elem in enumerate(it.content):
      elem="%s"%(elem)
      w=len(elem)*10+8
      rect(x,y,w,22,(BD,SL)[hilight*(i==it.selec)])
      rect(x+2,y+2,w-4,18,BG)
      dTxt(elem,x+4,y+2,(BD,FG)[i==it.selec],BG)
      x+=w
  def slide(it,x):
    it.selec+=x
    it.selec%=len(it.content)
class Btn(Elmnt):
  H,TYPE=24,"btn"
  def draw(it,x,y,hilight=0):
    txt="→ %s"%(it.name)
    w=len(txt)*10+8
    rect(x,y,w,22,(BD,SL)[hilight])
    rect(x+2,y+2,w-4,18,BG)
    dTxt(txt,x+4,y+2,FG,BG)
class Sep(Elmnt):
  H,TYPE=2,"sep"
  def draw(it,x,y,hilight=0):rect(x,y,50,2,BD)
class Lbl(Elmnt):
  H,TYPE=18,"lbl"
  def draw(it,x,y,hilight=0):dTxt(it.name,x,y,FG,BG)
      
def menu(
  elems=[
Btn("Play"),
Sld("Level",list(range(0,10))),
Sld("Mode",["Solo","Versus"]),
Sld("Game type",["Survival","Mining"]),
Btn("Back")]):
  X,s=130,0
  while elems[s].TYPE in ("sep","lbl"):s=(s+1)%len(elems)
  def get_elem(name):
    for e in elems:
      if e.name==name:return e
  def draw():
    y=10
    for i,elem in enumerate(elems):
      elem.draw(X,y,s==i)
      y+=elem.H+4
  draw_bg()
  draw()
  j,t=0,cTime()
  setup_demo=lambda:TetrisGame(25,50,lvl=20,s=8,col="808080")
  demo=setup_demo()
  while 1:
    if cTime()-t>0.2:t=cTime();j+=1;draw_title(20,20,j)
    demo.inputs+=[choice("LRUD<>")]
    demo.process()
    if demo.status==DEATH:demo=setup_demo()
    scan=[k for k in (0,1,2,3,4) if key(k)]
    if scan==[]:continue
    if elems[s].TYPE=="sld":
      if 0 in scan:elems[s].slide(-1)
      if 3 in scan:elems[s].slide(1)
    if elems[s].TYPE=="btn" and 4 in scan:break
    up=0
    if 1 in scan:up=-1
    if 2 in scan:up=1
    s=(s+up)%len(elems)
    while elems[s].TYPE in ("sep","lbl"):s=(s+up)%len(elems)
    draw()
    sleep(0.1)
  mode=get_elem("Game type").selec+get_elem("Mode").selec*2
  diff=get_elem("Level").selec
  btn=elems[s].name
  return btn,mode,diff

btn,mode,diff=menu()
if btn=="Play":
  play_game(mode=mode,diff=diff)

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 <a href="https://www.numworks.com/legal/cookies-policy/">cookies policy</a>.