castle_defence.py

Created by mathieu-croslacoste

Created on March 14, 2025

16.5 KB

Simple tower defense game ! Uses a lot of memory.

Very unoptimised. A better version is available at https://my.numworks.com/python/mathieu-croslacoste/castle_defence_v7


from math import *
from ion import *
from random import *
from kandinsky import *
from time import *
can_place=False
w=0
M=100
HP=100
map_select=1
pos_y=0
selection=0
wave_on=""
frozen=False
e_count=[]
t_count=[]
t_a_count=[]
map=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,]
letters=["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
names=[]
for i in range(120):
  if len(names)<26:
    names.append(str(letters[len(names)]))
  elif len(names)<351:
    a=len(names)%26
    b=len(names)//26
    names.append(str(letters[b]+letters[a]))
letters.clear()
class ennemy():
  ennemy:  __slots__ = ("lvl","og_level","ex","ey","heading","speed")
  def __init__(self,level,speed):
    global e_count
    e_count.append(self)
    self.lvl=level
    self.og_level=level
    self.ex=1
    self.ey=34
    self.heading=90
    self.speed=speed
  def draw(self):
    r=0
    g=0
    b=0
    if self.lvl==1:
      r=20
      g=255
      b=0
    elif self.lvl==2:
      r=255
      g=127
      b=0
    elif self.lvl==3:
      r=255
      g=20
      b=0
    elif self.lvl==4:
      r=127
      g=0
      b=127
    elif self.lvl==5:
      r=20
      g=0
      b=255
    elif self.lvl==6:
      r=255
      g=255
      b=255
    if self.lvl>=1:
        fill_rect(self.ex,self.ey,4,4,color(r,g,b))
  def finished(self):
    global HP,e_count
    if self.lvl<=0:#dead?
      if self in e_count:
        self.remove_e()
    elif (self.ex>=246) and (self.ey>=166):#reached castle?
      HP-=(self.lvl*3)
      self.remove_e()
      redraw()
    return True
  def remove_e(self):
    global M,e_count
    e_count.remove(self)
    M+=self.og_level+3*self.speed-3+randint(0,2)
    u_s()
  def move(self):
    if self.finished():
      if self.heading==90:
        if (not (get_pixel((self.ex-1),(self.ey-3))==color(130,90,5))):
          if (not (get_pixel((self.ex-1),(self.ey+7))==color(130,90,5))):
            self.ex+=self.speed
          else:
            self.heading=180
            self.ey+=8
        else:
          self.heading=0
          self.ey-=8
      elif self.heading==270:
        if (not (get_pixel((self.ex+1),(self.ey-3))==color(130,90,5))):
          if (not (get_pixel((self.ex+1),(self.ey+7))==color(130,90,5))):
            self.ex-=self.speed
          else:
            self.heading=180
            self.ey+=8
            self.ex-=2
        else:
          self.heading=0
          self.ey-=8
      elif self.heading==0:
        if (not (get_pixel((self.ex-3),(self.ey+5))==color(130,90,5))):
          if (not (get_pixel((self.ex+7),(self.ey+5))==color(130,90,5))):
            self.ey-=self.speed
          else:
            self.heading=90
            self.ex+=8
        else:
          self.heading=270
          self.ex-=8
      elif self.heading==180:
        if (not (get_pixel((self.ex-5),(self.ey-1))==color(130,90,5))):
          if (not (get_pixel((self.ex+8),(self.ey-1))==color(130,90,5))):
            self.ey+=self.speed
          else:
            self.heading=90
            self.ex+=8
        else:
          self.heading=270
          self.ex-=8
class t_attack:
  def __init__(self,x,y,damage,target,type):
    global t_a_count
    t_a_count.append(self)
    self.x=x
    self.y=y
    self.damage=damage
    self.target=target
    self._type=type
  def draw_attack(self):
    if self._type=="normal":
      r(self.x,self.y,3,3,110,110,110)
    elif self._type=="double":
      r(self.x,self.y,2,2,120,120,120)
    elif self._type=="fire":
      r(self.x,self.y,3,3,255,155,155)
    elif self._type=="ice":
      r(self.x,self.y,3,3,155,155,255)
  def move_attack(self):
    if self.x<self.target.ex:
      if self.y<self.target.ey:
        self.x+=4
        self.y+=4
      elif self.y>self.target.ey:
        self.x+=4
        self.y-=4
      else:
        self.x+=8
    elif self.x>self.target.ex:
      if self.y<self.target.ey:
        self.x-=4
        self.y+=4
      elif self.y>self.target.ey:
        self.x-=4
        self.y-=4
      else:
        self.x-=8
    elif self.y<self.target.ey:
      self.y+=8
    elif self.y>self.target.ey:
      self.y-=8
    hit_target(self)
    self.draw_attack()
def hit_target(self):
  global t_a_count,frozen
  if (self.x-4<self.target.ex<self.x+8) and (self.y-4<self.target.ey<self.y+8):
    t_a_count.remove(self)
    self.target.lvl-=self.damage
    if self._type=="ice":
      frozen=True
class t():
  def __init__(self,x,y,dmg,rel,range,type):
    global t_count
    t_count.append(self)
    self.tx=x
    self.ty=y
    self.dmg=dmg
    self.rel=rel
    self.r_t=rel
    self.ran=range
    self.type=type
    self.level=1
    if type=="normal":
      self.c=25
    elif type=="double":
      self.c=60
    elif type=="fire":
      self.c=130
    elif type=="ice":
      self.c=350
  def draw_t(self):
    if self.type=="normal":
      r((self.tx+1),(self.ty+1),6,6,70,70,70)
      r((self.tx+3),(self.ty+4),2,5,120,120,120)
    elif self.type=="double":
      r((self.tx+1),(self.ty+1),6,6,70,70,70)
      r((self.tx+1),(self.ty+4),2,4,120,120,120)
      r((self.tx+4),(self.ty+4),2,4,120,120,120)
    elif self.type=="fire":
      r((self.tx+1),(self.ty+1),6,6,200,50,15)
      r((self.tx+3),(self.ty+4),2,5,70,160,10)
    elif self.type=="ice":
      r((self.tx+1),(self.ty+1),6,6,80,80,180)
      r((self.tx+3),(self.ty+4),2,5,30,30,180)
  def do_t_attack(self):
    if self.r_t>=self.rel:
      e_name=0
      for e in e_count:
        if ((self.tx+4-self.ran)<=e.ex<=(self.tx+4+self.ran)) and ((self.ty-4-self.ran)<=e.ey<=(self.ty-4+self.ran)):
          if e_name==0:
            e_name=e
      if e_name!=0:
        names[len(t_a_count)]=t_attack(self.tx,self.ty,self.dmg,e_name,self.type)
        self.r_t=0
    else:
      self.r_t+=1
  def upgrade(self,upg_type):
    global M
    if self.type=="normal":
      if M>=25 and self.level==1:
        self.level+=1
        M-=25
        self.c=60
        self.rel-=4
        self.ran+=12
      elif M>=60 and self.level==2:
        self.level+=1
        M-=60
        self.c=150
        self.dmg+=1
      elif M>=150 and self.level==3:
        self.level+=1
        M-=150
        self.c=350
        self.rel-=5
        self.ran+=8
      elif M>=350 and self.level==4:
        self.level+=1
        M-=350
        self.c="MAX"
        if upg_type==1:
          self.dmg+=5
          self.rel+=12
          self.ran+=32
        else:
          self.dmg+=1
          self.rel-=8
          self.ran+=8
    elif self.type=="double":
      if M>=60 and self.level==1:
        self.level+=1
        M-=60
        self.c=150
        self.rel-=2
        self.ran+=12
      elif M>=150 and self.level==2:
        self.level+=1
        M-=150
        self.c=350
        self.dmg+=1
      elif M>=350 and self.level==3:
        self.level+=1
        M-=350
        self.c=850
        self.rel-=3
        self.ran+=8
      elif M>=850 and self.level==4:
        self.level+=1
        M-=850
        self.c="MAX"
        if upg_type==1:
          self.dmg-=1
          self.rel-=8
          self.ran+=24
        else:
          self.dmg+=4
          self.ran+=8
    elif self.type=="fire":
      if M>=130 and self.level==1:
        self.level+=1
        M-=130
        self.c=300
        self.rel-=4
        self.ran+=12
      elif M>=300 and self.level==2:
        self.level+=1
        M-=300
        self.c=700
        self.dmg+=2
      elif M>=700 and self.level==3:
        self.level+=1
        M-=700
        self.c=2000
        self.rel-=8
        self.ran+=8
      elif M>=2000 and self.level==4:
        self.level+=1
        M-=2000
        self.c="MAX"
        if upg_type==1:
          self.rel-=12
        else:
          self.dmg+=1
          self.rel-=4
          self.ran+=16
    elif self.type=="ice":
      if M>=350 and self.level==1:
        self.level+=1
        M-=350
        self.c=850
        self.rel-=3
        self.ran+=12
      elif M>=850 and self.level==2:
        self.level+=1
        M-=850
        self.c=2000
        self.dmg+=2
      elif M>=2000 and self.level==3:
        self.level+=1
        M-=2000
        self.c=4500
        self.rel-=5
        self.ran+=16
      elif M>=4500 and self.level==4:
        self.level+=1
        M-=4500
        self.c="MAX"
        if upg_type==1:
          self.rel-=9
          self.ran+=16
          self.dmg-=4
        else:
          self.rel+=26
          self.ran+=40
          self.dmg+=10
    map_selection(-1)

def update_et():
  global frozen
  if (not frozen) or (randint(1,2)==1):
    draw_path()
    for e in e_count:
      e.move()
      e.draw()
  frozen=False
  sleep(0.025)
  for i in t_count:
    i.do_t_attack()
  for a in t_a_count:
    a.move_attack()
def z(nb,lvl,speed,a):
  for i in range(nb):
    names[(i+a)]=ennemy(lvl,speed)
    for i in range(9-3*speed):
      update_et()
def start_w():
  global w
  if w==0:
    w+=1
    u_s()
    z(4,1,1,0)
  elif w==1:
    w+=1
    u_s()
    z(3,1,1,0)
    z(3,2,1,2)
  elif w==2:
    w+=1
    u_s()
    z(6,1,2,0)
    z(6,2,1,5)
  elif 30>w>2:
    w+=1
    u_s()
    z(w//4+1+w//16,w//4,2,0)
    z(w//3+1+w//16,w//3,1,w//4+w//16)
    z(w//4+2+w//16,w//3+w%3,1,w//4+w//3+w//16*2)
  elif w>29:
    w+=1
    u_s()
    z(w//8+w//16,w//5,2,0)
    z(w//4+w//16,w//4,2,w//4+w//16)
    z(w//3+1+w//16,w//3,1,w//4+w//3+w//16*2+1)
    z(w//4+w//16,w//3+w%3,1,w//4+w//3+w//4+w//16*3+2)
def redraw():
  r(0,0,320,200,0,0,0)
  u_s()
  draw_castle()
  draw_path()
  draw_button()
  for e in e_count:
    e.draw()
  for a in t_a_count:
    a.draw_attack()
  for t in t_count:
    t.draw_t()
def map_selection(key):
  global map_select,map,can_place,selection,pos_y,wave_on
  upg=""
  redraw()
  if key==0:
    map_select-=1
  if key==1:
    map_select-=40
  if key==2:
    map_select+=40
  if key==3:
    map_select+=1
  if map_select<0:
    map_select+=len(map)
  if map_select>=len(map):
    map_select-=(len(map))
  selection=map_select
  pos_y=1
  while selection>40:
    selection-=40
    pos_y+=8
  if map[map_select]==0:
    can_place=True
    for i in t_count:
      if i.tx==selection*8 and i.ty==pos_y:
        can_place=False
        upg=i
    if can_place:
      r(selection*8+1,pos_y,6,6,200,200,200)
      t_costs()
    elif upg.level<4:
      r(selection*8+1,pos_y,6,6,160,160,255)
      init_disp()
      s("level "+str(upg.level)+". "+str(upg.c)+" to level up (toolbox)",0,205,200,100,50,0,0,0)
    elif upg.level==4:
      r(selection*8+1,pos_y,6,6,255,100,255)
      init_disp()
      s("level 4. "+str(upg.c)+" to max ([(] or [)])",0,205,200,100,50,0,0,0)
    elif upg.level==5:
      init_disp()
      s("level MAX",0,205,200,100,50,0,0,0)
      r(selection*8+1,pos_y,6,6,255,160,160)
    else:
      upg=""
      r(selection*8+1,pos_y,6,6,255,160,160)
      t_costs()
  elif map[map_select]==2:
    can_place=False
    r(selection*8+1,pos_y,6,6,200,255,200)
  else:
    can_place=False
    r(selection*8+1,pos_y,6,6,255,200,200)
  if key==4:
    if (wave_on=="") and (map[map_select]==2):
      wave_on="-in progress"
      start_w()
  elif upg!="":
    if key==16 and upg.level<4:
      upg.upgrade(0)
      sleep(0.5)
    elif (key==33 or key==34):
      upg.upgrade(key-32)
      sleep(0.5)
  else:
    sleep(0.038)
def r(a,b,c,d,e,f,g):
  fill_rect(a,b,c,d,color(e,f,g))
def b():
  r(0,0,320,230,0,0,0)
def a():
  while not keydown(4): pass
  while keydown(4): pass
def draw_castle():
  r(238,170,30,30,70,70,70)
  for i in range(0,30,10):
    r((239+i),163,7,7,70,70,70)
  for i in range(0,13,12):
    r((242+i),174,9,9,0,0,0) 
  for i in range(4):
    r((248+i),(190-i),(10-2*i),10,149,69,49)
def init_disp():
  r(0,200,230,50,0,0,0)
def s(a,b,c,d,e,f,g,h,i):
  draw_string(a,b,c,color(d,e,f),color(g,h,i))
def t_costs():
  init_disp()
  s("50:  100:  200:  500:",0,205,200,100,50,0,0,0)
  r(33,205,12,12,70,70,70)
  r(37,211,4,9,120,120,120)
  r(93,205,12,12,70,70,70)
  r(95,212,3,8,120,120,120)
  r(100,212,3,8,120,120,120)
  r(153,205,12,12,200,50,15)
  r(157,211,4,10,200,100,50)    
  r(213,205,12,12,80,80,180)
  r(217,211,4,10,30,30,180)
def buy_t(key):
  global M,can_place
  total_levels=0
  for i in t_count:
    total_levels+=i.level
  if len(t_count)*2>=total_levels and len(t_count)>10:
    s("upgrade your turrets first",25,50,200,10,25,0,0,0)
    sleep(2)
    redraw()
  else:
    if key==42:
      if M>=50 and can_place:
        place_t(selection*8,pos_y,1,42,56,"normal")
        M-=50
        can_place=False
    elif key==43:
      if M>=100 and can_place:
        place_t(selection*8,pos_y,1,18,40,"double")
        M-=100
        can_place=False
    elif key==44:
      if M>=200 and can_place:
        place_t(selection*8,pos_y,3,40,56,"fire")
        M-=200
        can_place=False
    elif key==36:
      if M>=500 and can_place:
        place_t(selection*8,pos_y,3,40,72,"ice")
        M-=500
        can_place=False
    redraw()
  sleep(0.2)
def place_t(x,y,damage,reload,range,type):
  names[len(t_count)]=t(x,y,damage,reload,range,type)
  names[len(t_count)].draw_t()
  map_selection(-1)
def draw_button():
  r(296,0,24,24,140,140,140)
  for i in range(10):
    r(303+i,2+i,1,17-2*i,250,250,250)
def draw_path():
  r(0,32,112,8,130,90,5)
  r(104,40,8,32,130,90,5)
  r(32,72,80,8,130,90,5)
  r(24,72,8,64,130,90,5)
  r(32,128,152,8,130,90,5)
  r(176,56,8,72,130,90,5)
  r(184,56,72,8,130,90,5)
  r(248,64,8,104,130,90,5)
def control():
  for k in (42,43,44,36):
      if keydown(k):
        buy_t(k)
  for i in (0,1,2,3,4,16,33,34):
    if keydown(i):
      map_selection(i)
def u_s():
  s("Money "+str(M),200,0,200,100,50,0,0,0)
  s("Wave "+str(w)+wave_on,0,0,200,10,25,0,0,0)
  if (HP<100) and (HP>9):
    s("HP : "+str(HP),238,203,0,0,0,0,0,0)
    s("HP : "+str(HP),250,203,200,10,25,0,0,0)
  elif (HP<10):
    s("HP : "+str(HP),250,203,0,0,0,0,0,0)
    s("HP : "+str(HP),260,203,200,10,25,0,0,0)
  else:
    s("HP : "+str(HP),238,203,200,10,25,0,0,0)

b()
s("Castle Defence",87,80,200,10,25,0,0,0)
sleep(0.4)
s("Press [OK] to continue",52,125,200,10,25,0,0,0)
a()
b()
s("Select with arrow keys",48,8,200,10,25,0,0,0)
s("1,2,3 and 4 to place turrets",21,32,200,10,25,0,0,0)
s("(1 cost 50,2 cost 100...)",32,48,200,10,25,0,0,0)
s("Select the play button and",29,72,200,10,25,0,0,0)
s("press [OK] to start next wave",17,88,200,10,25,0,0,0)
s("Keep [OK] pressed during a wave",9,112,200,10,25,0,0,0)
s("to see the turrets attacks",34,128,200,10,25,0,0,0)
s("better, but the quality",52,144,200,10,25,0,0,0)
s("may be affected",84,160,200,10,25,0,0,0)
s("press toolbox/parenthesis to upg",0,184,200,10,25,0,0,0)
s("Press [OK] to start",64,208,200,10,25,0,0,0)
a()
b()
redraw()
t_costs()
while HP>0:
  while len(e_count)>0:
    update_et()
    control()
    if HP<=0:
      break
  wave_on=""
  for i in t_a_count:
    t_a_count.remove(i)
    redraw()
    u_s()
  control()
  update_et()
b()
s("GAME OVER",120,50,200,10,25,0,0,0)
s("you lost at wave "+str(w),70,100,200,10,25,0,0,0)

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.