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 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+u,y+u,s-2*u,s-2*u,c)
def draw_void(x,y,w,h,c):
rect(x,y,w,h,c)
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)
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):
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.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()

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=[]

it.ttr_lst.pop(0)
it.ttr_lst+=[choice(TETRA)]
it.tetra_2grid()
lines=len(it.detect_line())
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)
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)

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