A small upgrade of the big upgrade of RU N (originally by Fime
(https://my.numworks.com/python/fime/ruin),
then updated by ATOME (https://my.numworks.com/python/atome/ruin_boss_update))
from math import sin,cos,pi,sqrt,copysign from kandinsky import fill_rect as F,draw_string as D#,get_pixel as getPxl from ion import keydown as K from time import monotonic as cTime,sleep as S from random import randint as rInt TARG_SPF=0.02#50fps ref=10 BG_COL,PL_COL,PTF_COL,NMY_COL,NMY_COL2,BOSS_COL,k=(75,40,40),(240,10,10),(200,100,100),(0,150,255),(0,50,200),(255,50,0),(0,)*3 SAVEFILE="ruin.sav" def save_prog(lvl,frames): try:f=open(SAVEFILE,"w");f.truncate(0);f.write("%s\n%s"%(lvl,frames)) except:return def load_prog(): try: with open(SAVEFILE,"r")as f:lvl=int(f.readline());frames=int(f.readline());return lvl,frames except:return 0,0 class Entity(): def __init__(it,x,y,w,h,drawFonc):it.x,it.y,it.w,it.h=x,y,w,h;it.draw=lambda:drawFonc(it) def hitBox(it,it2): if it.x<it2.x+it2.w and it2.x<it.x+it.w and it.y<it2.y+it2.h and it2.y<it.y+it.h:return 1 return 0 class Platform(Entity): def __init__(it,x,y,w,h):super().__init__(x,y,w,h,None) def hitBox(it,it2): if super().hitBox(it2)and it2.y+it2.h<it.y+it.h:return 1 return 0 class Movable(Entity): def __init__(it,*arg):super().__init__(*arg);it.vx,it.vy,it.grounded=0,0,0 def applyPhysics(it): if it.grounded: it2=it.grounded;it.vy=0 if it.x>it2.x+it2.w or it2.x>it.x+it.w:it.grounded=0 else:it.vy+=.2;it.y+=min(it.vy,4) it.vx/=1.3;it.x+=it.vx;it.x=max(min(it.x,320-it.w),0) def onPlat(it,plat): if plat.hitBox(it)and 1-it.grounded and it.vy>0:it.grounded=plat;it.vy,it.y=0,plat.y-it.h def jump(it):it.vy,it.grounded=-4.3,0 class Enemy(Movable): def __init__(it,*arg):super().__init__(*list(arg)+[drawEnemy]) def applyPhysics(it): if it.grounded: it2=it.grounded if it2.w>20: if it.x<it2.x+it.w:it.vx+=.05 elif it.x>it2.x+it2.w-it.w*2:it.vx-=.05 elif it.vx==0:it.vx=1 super().applyPhysics();it.vx*=1.3 class Boss(Movable): def __init__(it,x,y,final=0):super().__init__(x,y,30,30,drawBoss);it.hp=5 if final else 3;it.max_hp=it.hp;it.final=final;it.invincible_frames=0;it.cldwn=0 def applyPhysics(it): if it.grounded: it2=it.grounded if rInt(0,35)==0:it.jump();it.vx+=rInt(-6,6) if it.x<50:it.vx+=1 elif it.x>250:it.vx-=1 else:it.vx+=rInt(-2,2)/10 super().applyPhysics();it.vx*=1.2 if it.invincible_frames>0:it.invincible_frames-=1 if it.cldwn>0:it.cldwn-=1 def takeDamage(it): if it.invincible_frames==0:it.hp-=1;it.invincible_frames=60;it.vy=-5;return 1 return 0 class Bullet(Movable): def __init__(it,it2,input_vy):super().__init__(it2.x+it2.w/2,it2.y+it2.h/2,3,3,drawBullet);it.vx,it.vy=copysign(3,it2.vx)+it2.vx,it2.vy+input_vy*2-1 def applyPhysics(it):it.vy+=.2;it.x+=it.vx;it.y+=it.vy class BossBullet(Entity): def __init__(it,x,y,vx,vy): super().__init__(x,y,5,5,drawBossBullet) if rInt(0,4):it.vx,it.vy,it.targ=vx,vy,0 else:it.vx,it.vy,it.targ=1,1,85 def update(it,pl): if it.targ: it.x+=copysign(it.vx,pl.x-it.x);it.y+=copysign(it.vy,pl.y-it.y) if it.targ==1:return 1 it.targ-=1 else:it.x+=it.vx;it.y+=it.vy return 0 def fade(col1,col2,n):return tuple([i1*(1-n)+i2*n for i1,i2 in zip(col1,col2)]) def shadow(it,is_boss=0): for i in range(1,6-3*is_boss):F(int(it.x-i*2),int(it.y+i*it.h),it.w-i,it.h,fade(k,BG_COL,i/6)) def drawBaseEntity(it,col):F(int(it.x+2),int(it.y),it.w-2,it.h,col);F(int(it.x),int(it.y),2,it.h,k) def dPl(it): drawBaseEntity(it,PL_COL);F(int(it.x+it.w/2),int(it.y+7),2,3,k) for i in(3,it.h-3):F(int(it.x+i+it.vx/4),int(it.y+2),2,2,k) def drawEnemy(it): drawBaseEntity(it,NMY_COL);F(int(it.x+2),int(it.y+it.h/2),it.w-2,it.h//2,NMY_COL2) for i in(3,it.h-3):F(int(it.x+i),int(it.y+it.h/2-2),2,5,k) def drawBoss(it): col=it.invincible_frames%10>=5 and(255,150,0)or it.grounded and BOSS_COL or fade(BG_COL,BOSS_COL,.2);F(int(it.x+2),int(it.y),it.w-2,it.h,col);F(int(it.x),int(it.y),2,it.h,k) for i in(0,1,2):F(int(it.x+5+i*8),int(it.y+5),5,5,k) mouth_w=14 if 1-it.final else 20;F(int(it.x+8),int(it.y+20),mouth_w,5,k);D(str(it.hp),int(it.x+it.w/2-5),int(it.y-15),PTF_COL,BG_COL) def drawBossBullet(it): if it.targ:c=fade(BG_COL,BOSS_COL,it.targ/86) else:c=BOSS_COL F(int(it.x),int(it.y),it.w,it.h,c) def hideEntity(it):F(int(it.x),int(it.y),int(it.w),int(it.h),BG_COL) def drawPlatform(it,is_boss):drawBaseEntity(it,PTF_COL);shadow(it,is_boss) def drawDoor(it): F(int(it.x),int(it.y),it.w,it.h,k) for i in range(1,5):F(int(it.x+1),int(it.y+it.h-i*2),int(it.w/2-i*2+3),1,fade(PTF_COL,k,i/6)) def drawBullet(it):F(int(it.x),int(it.y),it.w,it.h,PTF_COL) def createDoor(l1):x,y,w,h=l1;x,y,w,h=x+w//2-7.5,y-15,15,15;return Entity(x,y,w,h,drawDoor) def createPlayer(l1):x,y,w,h=l1;y,w,h=y-10,10,10;return Movable(x,y,w,h,dPl) def createEnemy(l1,id):x,y,w,h=l1;x,y,w,h=x+10*id,y-10,10,10;return Enemy(x,y,w,h) def unpackPlatforms(n,type): string=levels(type)[n];Hex2Int=lambda n:int("0x"+n);level=[] for i in range(0,len(string),9): ptf_str=string[i:i+9];plat=[] for j in range(0,8,2):plat+=[Hex2Int(ptf_str[j:j+2])*5] plat.append(int(ptf_str[-1]));level.append(plat) return level def loadLevel(lvl,type): pl=Movable(30,170,10,10,dPl) if lvl==2:platforms=[Platform(20,180,280,10),Platform(50,140,220,10),Platform(80,100,160,10)];boss=Boss(150,60);return[pl,platforms,[boss],None,1] if lvl==5:platforms=[Platform(10,190,100,10),Platform(210,190,100,10),Platform(80,140,160,10),Platform(120,90,80,10)];boss=Boss(150,80);return[pl,platforms,[boss],None,1] if lvl==8:platforms=[Platform(5,200,70,10),Platform(120,200,80,10),Platform(245,200,70,10),Platform(60,150,200,10),Platform(100,100,120,10)];boss=Boss(150,90);return[pl,platforms,[boss],None,1] plat1=[Platform(10,200,60,10),Platform(130,200,60,10),Platform(250,200,60,10),Platform(40,160,80,10),Platform(200,160,80,10),Platform(100,120,120,10)] if lvl==11:boss=Boss(150,110);return[pl,plat1,[boss],None,1] if lvl==14:platforms=[Platform(20,200,80,10),Platform(220,200,80,10),Platform(70,160,180,10),Platform(110,110,100,10)];boss=Boss(150,100);return[pl,platforms,[boss],None,1] if lvl==17:platforms=[Platform(10,210,90,10),Platform(120,210,80,10),Platform(220,210,90,10),Platform(50,170,220,10),Platform(90,120,140,10)];boss=Boss(150,110);return[pl,platforms,[boss],None,1] if lvl==20:boss=Boss(140,60,final=1);return[pl,plat1+[Platform(60,70,200,10)],[boss],None,1] del pl;file=unpackPlatforms(lvl,type);platforms,enemies=[],[] for i in file: platforms+=[Platform(*i[0:4])] if i[4]:enemies+=[createEnemy(i[0:4],j)for j in range(i[4])] pl,door=createPlayer(file[0][0:4]),createDoor(file[-1][:4]);return[pl,platforms,enemies,door,0] def shortTransition(): for i in range(30):F(0,0,320,222,fade(BG_COL,k,sin(i/30))) def rK(x): while K(x):0 class GameEngine(): def __init__(it,lvl_type="original"):it.status,it.lvl_type,it.lvl_nb=0,lvl_type,21;it.lvl,it.total_frame=load_prog() def nextLevel(it): if it.status==2:it.playTransition(mode=1);it.lvl+=1;save_prog(it.lvl,it.total_frame);it.playTransition() else:shortTransition() def endMsg(it): F(0,0,320,222,BG_COL);t=it.total_frame*TARG_SPF;msg=["You defeated all bosses!","","Levels: 21","Virtual time: {} min {}s".format(int(t//60),round(t%60,2)),"","Congratulations!"] for i,txt in enumerate(msg):D(txt,160-len(txt)*5,111-(len(msg)//2-i)*20,PTF_COL,BG_COL) save_prog(0,0);it.lvl,it.frame_nb=0,0 while 1-K(4):D("Press [OK]",210,200,fade(BG_COL,PL_COL,abs(sin(cTime()))),BG_COL) def playTransition(it,h=222,mode=0): try:l=loadLevel(it.lvl,it.lvl_type) except:return if l[4]:return entities,s_init,y=l[1]+[l[3]],20,int(h) if not mode: entities+=[l[0]]+l[2] for e in entities:e.y-=y del l while 1: if y<1:break if mode:s=(1-y/h)*s_init+5 else:s=sqrt(y/h)*s_init y=round(y-s,1);S(TARG_SPF*2);F(0,0,320,222,BG_COL) for e in entities: e.y+=s if type(e)==Platform:drawPlatform(e,0) else:e.draw() def playLevel(it): global ref,TARG_SPF;F(0,0,320,222,BG_COL);pl,platforms,enemies,door,is_boss=loadLevel(it.lvl,it.lvl_type);frame_nb,shot_frame,bullets,boss_b,it.status=0,0,[],[],0;boss=enemies[0]if is_boss else None if is_boss:boss_txt="FINAL BOSS!"if it.lvl==20 else"BOSS FIGHT!";D(boss_txt,160-len(boss_txt)*5,20,BOSS_COL,BG_COL);S(1);F(0,20,320,20,BG_COL) while it.status==0: t=cTime();frame_nb+=1;pl.vx+=K(3)-K(0);pl.vy+=.1*(1-K(4)) if(frame_nb+9)%10==0 and door:door.draw() for b in bullets: hideEntity(b);b.applyPhysics() if b.y<0 or b.x<0 or b.x>320:bullets.remove(b) else:b.draw() for bb in boss_b: hideEntity(bb) if bb.update(pl)or bb.y>222 or bb.y<0 or bb.x<0 or bb.x>320:boss_b.remove(bb);continue bb.draw() if bb.hitBox(pl):it.status=1;break for mov in enemies+[pl]: hideEntity(mov) if mov!=pl and 1-is_boss: if mov.hitBox(pl):it.status=1;break for b in bullets: if b.hitBox(mov):mov.vx+=b.vx/3;mov.vy+=b.vy/3 if mov.y>222:enemies.remove(mov) elif mov==boss: F(int(mov.x+mov.w/2-5),int(mov.y-15),10,15,BG_COL) if mov.y>222:it.status=2;break if pl.vy>0 and pl.y+pl.h<=boss.y+10 and boss.hitBox(pl): if boss.takeDamage(): pl.vy=-5+boss.vy/4 if boss.hp==0:it.status=2;break boss.cldwn=60 for j in range(8):a=j*pi/4;boss_b.append(BossBullet(boss.x+boss.w/2,boss.y+boss.h/2,cos(a)*2,sin(a)*2)) if boss.final: for j in range(8):a=j*pi/4+pi/8;boss_b.append(BossBullet(boss.x+boss.w/2,boss.y+boss.h/2,cos(a)*1.5,sin(a)*1.5)) elif boss.grounded and boss.hitBox(pl):it.status=1;break if boss.cldwn==0: boss.cldwn=boss.final and 90 or 120;n=boss.final and 6 or 4 for j in range(n):a=pi/4+j*2*pi/n;boss_b.append(BossBullet(boss.x+boss.w/2,boss.y+boss.h/2,cos(a)*1.5,sin(a)*1.5)) mov.applyPhysics() for plat in platforms: mov.onPlat(plat) if(frame_nb+9)%ref==0 and mov==pl:drawPlatform(plat,is_boss) mov.draw() if frame_nb-shot_frame>10 and K(16)and 1-is_boss:shot_frame=frame_nb;bullets+=[Bullet(pl,K(2)-K(1))] if pl.grounded: if K(4):pl.jump() if door and pl.hitBox(door)==1:it.status=2 if pl.y>222:it.status=1 while cTime()-t<TARG_SPF:0 if K(17): D(" P A U S E D ",95,101,BG_COL,PTF_COL);frame_nb+=10-frame_nb%10;rK(17);s=0 while 1-K(17): D("SPF (Sec/Frame): "+str(TARG_SPF)+" ",5,121,1-s and BOSS_COL or BG_COL,PTF_COL);D("Redraw rate (shadow): "+str(ref)+" ",5,141,s and BOSS_COL or BG_COL,PTF_COL) while K(0)+K(1)+K(2)+K(3):0 while 1-K(0)-K(1)-K(2)-K(3)-K(17):0 TARG_SPF+=(1-s)*(K(3)-K(0))/100;ref+=s*(K(3)-K(0)) if K(1)+K(2):s=K(2) rK(17);F(0,101,320,58,BG_COL) it.total_frame+=frame_nb def levels(lvl_type):return("011606010071632010391606010", "011606010111606010231606010341606010", 0, "042306010161c060100e1410011290f06011350f06010", "08280601026280a011342102010291b020100f1b0201003140a011190d0201026080a010", 0, "0527060101927010102a27010103420060112819060110c19060110511010100b09010101a060a013330606010", "0428060100c2001010041c010100c16010102f1e01010391801010301101010260b0601012070a010", 0, "052506010051e06011051606011050f06011050806011301806010", "0628060101623060110c1e020101719020102b19020103b12020102b0c02010170c02010060906010", 0, "0105040100c05350100823010101a1f060111e1801010341206011280e06010280e06010", "362706010292006011351a020102f1302010042006011041706011080f010100c0706010", 0, "042806010192306011251e06010311a060112514060100316060110a0e01010140806010", "3726060101f2601010042601010081e010101019010102519010102f1006011220b06010", 0, "0327060100c20010101619010102119030112d1903011351306010260a060111a0a01010050a06010", "0220060101d1b020113117020111c0f06010", ) y,s_init,p=150,20,0;F(0,0,320,222,BG_COL) while y>5: if K(4):p=1 s=(1-y/150)*s_init+p;y=round(y-s,1);S(TARG_SPF*2) if p:F(0,0,320,222,BG_COL) D(" R U | N ",115,int(251-y),BG_COL,PTF_COL);D(" by F | M E ",100,int(281-y),PTF_COL,BG_COL);D("Boss update by ATOME",60,int(301-y),BOSS_COL,BG_COL);D("[OK] to start",90,int(331-y),PL_COL,BG_COL) del y,s_init,p game=GameEngine(lvl_type="og") game.playTransition() while 1: if game.lvl>=game.lvl_nb:game.endMsg();return game.playLevel();game.nextLevel()