ruin_boss_update_v2.py

Created by mathieu-croslacoste

Created on November 21, 2025

11.8 KB

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()

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.