cosmic_raiders.py

Created by ccsio

Created on February 24, 2025

18.6 KB

My adaptation of those galaxy-shooter-style games. Quite customizable and sophisticated. Id like to add more stuff but MemoryError is a thing soo. Check out the code and documentation on GitHub: https://github.com/ccsio/cosmic_raiders


from kandinsky import fill_rect as fr,color as c,draw_string as ds,get_pixel as gp
from ion import keydown,KEY_RIGHT,KEY_LEFT,KEY_EXE,KEY_OK,KEY_ANS
from time import monotonic as mc,sleep as sp
from random import randrange 
black = c(0,0,0) 
white = c(248, 252, 248)
gray = c(56,60,56)
red = c(248,0,0)
blue = c(30,10,211)
green = c(30, 132, 73)
lime = c(24,209,18)
navy_blue = c(31,97,141)
action_list, moving_list, bullet_list, enemy_list = [], [], [], []
move_delay, fps_limit, action_delay = mc(), mc(), mc()
basic = ("basic",None,None) 
tank = ("tank",None,None)
shooterR = ("shooter",("r",115,5),None)
shooter5 = ("shooter",("f",5,5),None)
shooter3 = ("shooter",("f",3,5),None)
ram1 = ("ram",("r",200),("y",180,12,True,1))
ram2 = ("ram",("r",200),("y",144,12,True,1))
shield = ("shield",(200,5,3),None)
d = "dead"
level = 0
levels = ( 
(  (((d,)*8)), (d,basic,basic,d,d,basic,basic,d)),
(  (basic,basic,d,d,d,d,basic,basic), (basic,d,basic,basic,basic,basic,d,basic)),
(  (d,basic,basic,d,d,basic,basic,d), (basic,d,d,shooterR,shooterR,d,d,basic)),
(  (shooterR,basic,basic,d,d,basic,basic,shooterR), (d,d,basic,shooterR,shooterR,basic,d,d)),
(  (shooter5,basic,basic,shooterR,shooterR,basic,basic,shooter5), (basic,d,d,basic,basic,d,d,basic)),
(  (shooter3,shooterR,d,d,d,d,shooterR,shooter3), (basic,basic,basic,shooterR,shooterR,basic,basic,basic)),
(  (shooter5,d,basic,d,d,basic,d,shooter5), (basic,basic,d,tank,tank,d,basic,basic)),
(  (shooter5,basic,d,basic,basic,d,basic,shooter5), (tank,d,tank,shooterR,shooterR,tank,d,tank)),
(  (shooter3,basic,d,shooter5,shooter5,d,basic,shooter3), (tank,d,basic,tank,tank,basic,d,tank)),
(  (("shooter",("f",3,5),("x",84,4,True,1)),d,d,shooter3,shooter3,d,d,("shooter",("f",3,5),("x",84,-4,True,1))), (basic,basic,basic,tank,tank,basic,basic,basic)),\
(  (("basic",None,("y",36,3,True,1)), ("shooter",("r",100,8),("x",42,3,True,1)), d,shooter3,shooter3,d,("shooter",("r",100,8),("x",42,-3,True,1)),("basic",None,("y",36,3,True,1))), (d,d,tank,basic,basic,tank,d,d)),
(  (ram1,basic,basic,shooter3,shooter3,basic,basic,ram1), (d,basic,d,tank,tank,d,basic,d)),
(  (d,shooter5,shooterR,basic,basic,shooterR,shooter5,d), (ram2,tank,basic,ram2,ram2,basic,tank,ram2)),
(  (ram1,shooter3,d,shooterR,shooterR,d,shooter3,ram1), (d,tank,ram2,tank,d,ram2,tank,d)),
(  (ram1,basic,shooterR,shooterR,shooterR,shooterR,basic,ram1), (d,ram2,tank,basic,basic,tank,ram2,d)),
(  (shooter5,shield,shooter3,basic,basic,shooter3,shield,shooter5), (tank,basic,tank,d,d,tank,basic,tank)),
(  (shield,shooter5,basic,ram1,ram1,basic,shooter5,shield), ((("shooter",("f",3,5),("x",84,4,True,1))),d,d,d,d,d,d,(("shooter",("f",3,5),("x",84,-4,True,1))))),
(  (basic,shield,shooter3,shield,shooterR,shooter3,shield,basic), (ram2,tank,shooter5,tank,ram2,shooter5,tank,ram2)),
(  (shield,shooter3,("shooter",("r",100,10),None),d,shield,("shooter",("r",100,10),None),shooter3,shield), (ram2,tank,tank,ram2,ram2,tank,tank,ram2)),
(  (shooterR,("shooter",("f",2,8),None),("shield",(180,4,4),None),("ram",("r",150),("y",180,18,True,1)),("ram",("r",150),("y",180,18,True,1)),("shield",(180,4,4),None),("shooter",("f",2,8),None),shooterR), (basic,tank,shooter5,d,d,shooter5,tank,basic)),
)
def enemy_textures(x,y,col,bg=white):
    fr(x+2,y,4,2,black)
    fr(x+28,y,4,2,black)
    fr(x,y+2,34,10,black)
    fr(x+2,y+12,30,2,black)
    fr(x+6,y+14,22,2,black)
    fr(x+10,y+16,14,4,black)
    fr(x+12,y+20,10,4,black)
    fr(x+14,y+24,6,4,black)
    fr(x+16,y+28,2,2,black)
    fr(x+10,y+2,4,2,bg)
    fr(x+20,y+2,4,2,bg)
    fr(x+2,y+2,2,10,col)
    fr(x+30,y+2,2,10,col)
    fr(x+4,y+10,2,2,col)
    fr(x+28,y+10,2,2,col)
    fr(x+6,y+12,4,2,col)
    fr(x+24,y+12,4,2,col)
    fr(x+10,y+14,2,2,col)
    fr(x+22,y+14,2,2,col)
    fr(x+12,y+16,2,2,col)
    fr(x+20,y+16,2,2,col)
    fr(x+14,y+18,2,6,col)
    fr(x+18,y+18,2,6,col)
    fr(x+16,y+24,2,4,col)

class Bullet:
    def __init__(self,x,y,dir,speed,color=red):
        self.pos_x = x
        self.pos_y = y
        self.dir = dir
        self.speed = speed
        self.size = (2,6)
        self.col = color
        self.hidden = False
        fr(self.pos_x,self.pos_y,self.size[0],self.size[1],self.col)
        
    def delete_bullet(self,index):
        fr(self.pos_x,self.pos_y,self.size[0],self.size[1],white)
        del bullet_list[index]

class PlayerBullet(Bullet):
    def move(self,xx):
        if self.pos_y < 6:
            self.delete_bullet(xx)
            return
        elif gp(self.pos_x,self.pos_y-6) != white and self.pos_y < 70:
            for idx,i in reversed(list(enumerate(enemy_list))):
                if i == "dead":
                    continue
                elif i.pos_x <= self.pos_x <= i.pos_x + 34:
                    self.delete_bullet(xx)
                    if i.is_shielded:
                        i._draw_shield()
                        return
                    return idx
        fr(self.pos_x,self.pos_y,self.size[0],self.size[1],white)
        self.pos_y += self.dir*self.speed
        fr(self.pos_x,self.pos_y,self.size[0],self.size[1],self.col)

class EnemyBullet(Bullet):
    def move(self,xx):
        if self.hidden:
            if self.pos_y > 70:
                self.hidden = False
            else:
                self.pos_y += self.dir*self.speed
            return
        elif self.pos_y > 210:
            self.delete_bullet(xx)
            return

        elif gp(self.pos_x,self.pos_y+6) != white and self.pos_y < 150:
            for idx,i in reversed(list(enumerate(enemy_list))):
                if i == "dead":
                    continue
                elif i.pos_x <= self.pos_x <= i.pos_x + 34:
                    fr(self.pos_x,self.pos_y,self.size[0],self.size[1],white) 
                    enemy_list[idx].load_texture()
                    self.hidden = True
                    return
        elif gp(self.pos_x,self.pos_y+6) != white:
            self.delete_bullet(xx)
            return "player_hit"
        fr(self.pos_x,self.pos_y,self.size[0],self.size[1],white)
        self.pos_y += self.dir*self.speed
        fr(self.pos_x,self.pos_y,self.size[0],self.size[1],self.col)


class SpaceShip:
    def __init__(self,x,y,hitpoints,class_dat,move_dat):
        self.pos_x = x
        self.pos_y = y
        self.size = 34
        self.hp = hitpoints

    def hit(self) -> True or False:
        self.hp -= 1
        if self.hp <= 0:
            fr(self.pos_x,self.pos_y,34,34,white)
            return True
        return False

class Player(SpaceShip):
    def __init__(self,x,y,hitpoints,class_dat,move_dat):
        super().__init__(x,y,hitpoints,class_dat,move_dat)
        self.shot_delay = mc()
        self.speed = 3
        self.life_colors = [red, red, red]
        self.load_texture()

    def move_hor(self,dir):
        fr(self.pos_x,self.pos_y,self.size,self.size,white)
        self.pos_x += dir*self.speed
        self.load_texture()

    def load_texture(self):
        x,y = self.pos_x, self.pos_y
        fr(x+16,y,2,2,black)
        fr(x+14,y+2,6,4,black)
        fr(x+12,y+6,10,6,black)
        fr(x+10,y+12,14,2,black)
        fr(x+6,y+14,22,2,black)
        fr(x+2,y+16,30,2,black)
        fr(x,y+18,34,6,black)
        fr(x+2,y+24,30,2,black)
        fr(x+10,y+26,14,2,black)
        fr(x+16,y+4,2,4,self.life_colors[2])
        fr(x+14,y+8,2,6,self.life_colors[2])
        fr(x+18,y+8,2,6,self.life_colors[2])
        fr(x+12,y+14,2,2,self.life_colors[2])
        fr(x+20,y+14,2,2,self.life_colors[2])
        fr(x+2,y+18,2,6,self.life_colors[0])
        fr(x+4,y+22,6,2,self.life_colors[0])
        fr(x+10,y+24,4,2,self.life_colors[0])
        fr(x+30,y+18,2,6,self.life_colors[1])
        fr(x+24,y+22,6,2,self.life_colors[1])
        fr(x+20,y+24,4,2,self.life_colors[1])
    
    def hit(self) -> True or False:
        self.hp -= 1
        if self.hp <= 0:
            fr(self.pos_x,self.pos_y,34,34,white)
            return True
        self.life_colors[3 - self.hp] = gray 
        self.load_texture()
        return False
    
    def player_died(self):
        enemy_list.clear()
        action_list.clear()
        moving_list.clear()
        fr(0,0,320,240,white)
        ds("Game Over",115,100,black)
        global player; player = None

    def shoot(self):
        if mc() - self.shot_delay > 0.9:
            bullet_list.append(PlayerBullet(self.pos_x+16,self.pos_y-6,-1,5))
            self.shot_delay = mc()


class Enemy(SpaceShip):
    def __init__(self,x,y,hitpoints,class_dat,move_dat):
        super().__init__(x,y,hitpoints,class_dat,move_dat)
        self.is_shielded = False
        if move_dat is not None:
            self.axis = move_dat[0]
            self.total_distance = move_dat[1]
            self.px_update = move_dat[2]
            self.rounds = abs(self.total_distance/self.px_update)
            self.loop = move_dat[3]
            self.wait_time = move_dat[4]
            self.delay = 0
            self.dir = 1

    def _move_x(self):
        if self.is_shielded:
            self._draw_shield(white)
        fr(self.pos_x,self.pos_y,self.size,self.size,white)
        self.pos_x += int(self.dir * self.px_update)
        self.load_texture()
        if self.is_shielded:
            self._draw_shield()

    def _move_y(self,col=None):
        if self.is_shielded:
            self._draw_shield(white)
        fr(self.pos_x,self.pos_y,self.size,self.size,white)
        self.pos_y += int(self.dir * self.px_update)
        if col is not None:
            self.load_texture(col)
        else:
            self.load_texture()
        if self.is_shielded:
            self._draw_shield()

    def move(self):
        if mc() - self.delay < self.wait_time:
            return
        elif self.axis == "x":
            self._move_x()
        else:
            self._move_y()
        self.rounds -= 1
        if self.rounds == 0:
            if not self.loop:
                return True
            self.dir *= -1
            self.rounds = abs(self.total_distance / self.px_update)
            self.delay = mc()

    def shoot(self):
        bullet_list.append(EnemyBullet(self.pos_x+16,self.pos_y+36,1,self.bullet_speed))

    def _draw_shield(self,col=blue):
        x,y, = self.pos_x, self.pos_y
        fr(x+16,y+32,2,2,col)
        fr(x+14,y+30,2,2,col)
        fr(x+18,y+30,2,2,col)
        fr(x+12,y+28,2,2,col)
        fr(x+20,y+28,2,2,col)
        fr(x+10,y+24,2,4,col)
        fr(x+22,y+24,2,4,col)
        fr(x+8,y+20,2,4,col)
        fr(x+24,y+20,2,4,col)
        fr(x+6,y+18,2,2,col)
        fr(x+26,y+18,2,2,col)
        fr(x+2,y+16,4,2,col)
        fr(x+28,y+16,4,2,col)
        fr(x,y+14,2,2,col)
        fr(x+32,y+14,2,2,col)

class BasicShip(Enemy):
    def load_texture(self):
        enemy_textures(self.pos_x,self.pos_y,(160, 100, 0))

class TankShip(Enemy):
    def load_texture(self):
        enemy_textures(self.pos_x,self.pos_y,(104, 4, 160))

class ShooterShip(Enemy):
    def __init__(self, x, y, hitpoints, class_dat, move_dat):
        super().__init__(x, y, hitpoints, class_dat, move_dat)
        self.shooting_type = class_dat[0]
        self.type_param = class_dat[1]
        self.bullet_speed = class_dat[2]
        self.shot_delay = 6

    def load_texture(self):
        enemy_textures(self.pos_x,self.pos_y,red)

    def action(self,idx):
        if self.shooting_type == "f" and mc() - self.shot_delay > self.type_param:
            self.shoot()
            self.shot_delay = mc()
        elif self.shooting_type == "r" and randrange(self.type_param) == 1:
            self.shoot()

class RammingShip(Enemy):
    def __init__(self, x, y, hitpoints, class_dat, move_dat):
        super().__init__(x, y, hitpoints, class_dat, move_dat)
        self.ram_type = class_dat[0]
        self.type_param = class_dat[1]
        self.ram_delay = 6
        self.is_ramming = False
        self.rounds += 10

    def load_texture(self,col=green):
        enemy_textures(self.pos_x,self.pos_y,col)

    def ram(self):
        if mc() - self.delay < self.wait_time:
            return
        elif player.pos_x +34 >= self.pos_x and player.pos_x < self.pos_x +34 and self.dir == 1 and self.pos_y > 130:
            if player.hit():
                player.player_died()
            else:
                fr(self.pos_x,self.pos_y,self.size,self.size,white)
                self.pos_y = 147
                self.load_texture()
                self.dir *= -1
                self.rounds = self.total_distance/ self.px_update -3
                self.delay = mc()
        elif self.total_distance / self.px_update < self.rounds:
            self.rounds -= 1
            return
        self._move_y(lime)
        self.rounds -= 1
        if self.rounds == 0:
            self.dir *= -1
            self.rounds = self.total_distance / self.px_update + 10
            if self.is_ramming and self.pos_y < 80:
                self.load_texture(green)
                self.is_ramming = False
                return
            self.delay = mc()

    def action(self,idx):
        if self.is_ramming:
            self.ram()
        elif self.ram_type == "f" and mc() - self.ram_delay > self.type_param:
            self.is_ramming = True
            self.load_texture(lime)
            self.ram()
        elif self.ram_type == "r" and randrange(self.type_param) == 1:
            self.is_ramming = True
            self.load_texture(lime)
            self.ram()

class ShieldingShip(Enemy):
    def __init__(self, x, y, hitpoints, class_dat, move_dat):
        super().__init__(x, y, hitpoints, class_dat, move_dat)
        self.shield_chance = class_dat[0]
        self.shield_time = class_dat[1]
        self.tot_ships = class_dat[2]

    def load_texture(self,col=navy_blue):
        enemy_textures(self.pos_x,self.pos_y,col)

    def _indexes(self):
        yield 0, 0
        yield -1, 1
        yield 1, 2
        yield 8, 3
        yield 7, 4
        yield 9, 5

    def activate_shield(self,idx):
        self.load_texture(blue)
        self.shield_timer = mc()
        for i,j in self._indexes():
            try:
                if j > self.tot_ships:
                    return
                elif enemy_list[idx+i] != "dead":
                    enemy_list[idx+i].is_shielded = True
                    enemy_list[idx+i]._draw_shield()
            except:
                continue

    def remove_shield(self,idx):
        self.load_texture()
        for i,j in self._indexes():
            if j > self.tot_ships:
                return
            elif enemy_list[idx+i] != "dead":
                enemy_list[idx+i].is_shielded = False
                enemy_list[idx+i]._draw_shield(white)
    
    def action(self,idx):
        if self.is_shielded and mc() - self.shield_timer > self.shield_time:
            self.remove_shield(idx)
        elif randrange(self.shield_chance) == 1 and not self.is_shielded:
            self.activate_shield(idx)

def place_enemies(data):
    for i in range(2):
        if len(data[i]) == 0:
            continue
        y_position = (i*36) + 3
        for j in range(8):
            x_position = j*40 + 2
            moving_dat = data[i][j][2]
            class_dat = data[i][j][1]

            if data[i][j] == "dead":
                enemy_list.append("dead")
                continue
            elif data[i][j][0] == "basic":
                enemy_list.append(BasicShip(x_position,y_position,1,class_dat,moving_dat))
            elif data[i][j][0] == "tank":
                enemy_list.append(TankShip(x_position,y_position,5,class_dat,moving_dat))
            elif data[i][j][0] == "shooter":
                enemy_list.append(ShooterShip(x_position,y_position,2,class_dat,moving_dat))
                action_list.append(enemy_list[8*i+j])
            elif data[i][j][0] == "ram":
                enemy_list.append(RammingShip(x_position,y_position,3,class_dat,moving_dat))
                action_list.append(enemy_list[8*i+j])
            elif data[i][j][0] == "shield":
                enemy_list.append(ShieldingShip(x_position,y_position,2,class_dat,moving_dat))
                action_list.append(enemy_list[8*i+j])

            if moving_dat is not None and not isinstance(enemy_list[-1],RammingShip):
                moving_list.append(enemy_list[8*i+j])

            enemy_list[8*i+j].load_texture()

def next_level(lvl):
    lvl += 1
    fr(0,0,320,240,white)
    _ = "Level "+str(lvl+1)
    ds(_,int(160 - 5*len(_)),100,black)
    sp(2)
    fr(0,0,320,240,white)
    place_enemies(levels[lvl])
    player.load_texture()
    return lvl

def show_tips():
    ds("< > to move",105,75,black)
    ds("EXE to shoot",100,95,black)
    ds("ANS to pause",100,115,black)
    while True:
        if keydown(KEY_RIGHT) or keydown(KEY_LEFT) or keydown(KEY_EXE):
            fr(0,74,320,60,white)
            break

def title_screen(text):
    fr(0,0,320,250,navy_blue)
    enemy_textures(250,32,red,navy_blue)
    enemy_textures(50,68,green,navy_blue)
    Player(32,184,4,None,None)
    fr(267,100,2,6,red)
    fr(267,142,2,6,red)
    fr(49,120,2,6,red)
    for i,j in zip(text,(85,105,150)):
        ds(i,int(160 - 5*len(i)),j,white,navy_blue)
    while True:
        if keydown(KEY_OK):
            fr(0,0,320,250,white)
            break


title_screen(("Cosmic Raiders","by Lukas Hlavac","(OK) to continue"))
player = Player(143,184,4,None,None)
place_enemies(levels[level])
show_tips()

while True:
    if keydown(KEY_RIGHT) and player.pos_x < 296:
        player.move_hor(1)
    elif keydown(KEY_LEFT) and player.pos_x > -12:
        player.move_hor(-1)
    if keydown(KEY_EXE):
        player.shoot()
    elif keydown(KEY_ANS):
        ds("ANS to unpause",90,100,black)
        sp(0.2)
        while True:
            if keydown(KEY_ANS):
                fr(0,100,320,20,white)
                break
        sp(0.2)

    for idx,i in enumerate(bullet_list):
        entity_hit = i.move(idx)
        if entity_hit == "player_hit":
            if player.hit():
                player.player_died()
        elif entity_hit is None:
            pass
        else:
            if enemy_list[entity_hit].hit():
                for i in [moving_list,action_list]:
                    if enemy_list[entity_hit] in i:
                        i.remove(enemy_list[entity_hit])

                enemy_list[entity_hit] = "dead"
                if enemy_list.count("dead") == len(enemy_list):
                    bullet_list, enemy_list, moving_list,= [], [], []
                    if level+1 == len(levels):
                        title_screen(("Congratualtions!","You won!"))
                    level = next_level(level)
                    action_delay = mc()
    if mc() - action_delay > 1:
        for i in action_list:
            i.action(enemy_list.index(i))
    if mc()-move_delay > 0.10:
        for i in moving_list:
            if i.move():
                moving_list.remove(i)
        move_delay = mc()
    if mc() - fps_limit > 0.05:
        pass
    else:
        sp(0.05 - (mc()- fps_limit))
    fps_limit = mc()

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.