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)