Space Blast
Bhathiya Perera
Hint: You need a keyboard to play this
# All assets from https://www.kenney.nl/assets/
# Code ported to Yaksha based on https://github.com/tashvit/space-blast
import raylib as rl
import raylib.utils
import libs.numbers as num
import libs.perlin
import libs.random
SCENE_TITLE: Const[u8] = 0u8
SCENE_PLAY: Const[u8] = 1u8
SCENE_GAME_OVER: Const[u8] = 2u8
GAME_W: Const[int] = 1920
GAME_H: Const[int] = 1080
STAR_COUNT: Const[int] = 100
ENEMY_COUNT: Const[int] = 20
FPS_TARGET: Const[int] = 60
PLAYER_BULLETS: Const[int] = 10
ENEMY_BULLET_MAX: Const[int] = 5
@onstack
class Star:
x: int
y: int
speed: int
r: int
@onstack
class Bullet:
x: int
y: int
@onstack
class Enemy:
x: int
y: int
speed: int
type: int
swing: int
bullet: Bullet
class State:
frame_count: u64
assets: Assets
player_x: int
player_y: int
speed: int
stars: Array[Star]
enemies: Array[Enemy]
scene: u8
player_moving: bool
player_bullets: Array[Bullet]
player_score: int
class Assets:
loaded: bool
bg: rl.Color
white: rl.Color
player: rl.Texture2D
enemy1: rl.Texture2D
enemy2: rl.Texture2D
enemy3: rl.Texture2D
enemy4: rl.Texture2D
enemy5: rl.Texture2D
meteor: rl.Texture2D
enemy_laser: rl.Texture2D
player_laser: rl.Texture2D
enemy_laser_sound: rl.Sound
player_laser_sound: rl.Sound
explosion_sound: rl.Sound
def update_stars(s: State) -> None:
for star: Star in s.stars:
star.y += star.speed
# if we are out of the screen recreate the star
if star.y > GAME_H + 10:
star.y = -10
star.x = rl.get_random_value(0, GAME_W)
star.r = rl.get_random_value(1, 4)
def update_player_bullets(s: State, pressed_fire: bool) -> None:
to_fire: bool = pressed_fire
for bullet: Bullet in s.player_bullets:
if bullet.y == -999 and to_fire and s.frame_count % 8u64 == 0u64:
bullet.y = s.player_y - 32
bullet.x = s.player_x - 32
rl.play_sound(s.assets.player_laser_sound)
to_fire = False
if bullet.y < 0:
bullet.y = -999
bullet.x = -999
if bullet.y != -999:
bullet.y -= 10
def update_player(s: State) -> None:
w2: int = cast("int", s.assets.player.width) / 2
h2: int = cast("int", s.assets.player.height) / 2
s.player_moving = False
# User pressed a key -> move the player
if rl.is_key_down(rl.KEY_W) or rl.is_key_down(rl.KEY_UP):
s.player_y -= s.speed
s.player_moving = True
if rl.is_key_down(rl.KEY_A) or rl.is_key_down(rl.KEY_LEFT):
s.player_x -= s.speed
s.player_moving = True
if rl.is_key_down(rl.KEY_S) or rl.is_key_down(rl.KEY_DOWN):
s.player_y += s.speed
s.player_moving = True
if rl.is_key_down(rl.KEY_D) or rl.is_key_down(rl.KEY_RIGHT):
s.player_x += s.speed
s.player_moving = True
update_player_bullets(s, rl.is_key_down(rl.KEY_SPACE))
# Ensure that the player width/height is constrained
# So we cannot go out of the bounds
if s.player_x <= w2:
s.player_x = w2
if s.player_x >= GAME_W - w2:
s.player_x = GAME_W - w2
if s.player_y <= h2:
s.player_y = h2
if s.player_y >= GAME_H - h2:
s.player_y = GAME_H - h2
# Check collisions with enemies
for enemy: Enemy in s.enemies:
enemy_collide: bool = collides(get_enemy_ship(s, enemy.type), enemy.x, enemy.y, s.assets.player, s.player_x, s.player_y)
bullet_collide: bool = collides(s.assets.enemy_laser, enemy.bullet.x, enemy.bullet.y, s.assets.player, s.player_x, s.player_y)
if enemy_collide or bullet_collide:
s.scene = SCENE_GAME_OVER
rl.play_sound(s.assets.explosion_sound)
break
for bullet: Bullet in s.player_bullets:
enemy_in_fire: bool = collides(get_enemy_ship(s, enemy.type), enemy.x, enemy.y, s.assets.player_laser, bullet.x, bullet.y)
bulllets_collide: bool = collides(s.assets.enemy_laser, enemy.bullet.x, enemy.bullet.y, s.assets.player_laser, bullet.x, bullet.y)
if bullet.y != -999 and enemy_in_fire:
s.player_score += 10
enemy.y = GAME_H + 50
bullet.y = -999
bullet.x = -999
rl.play_sound(s.assets.explosion_sound)
if bullet.y != -999 and bulllets_collide:
enemy.bullet.x = -999
enemy.bullet.y = -999
bullet.y = -999
bullet.x = -999
def update_enemies(s: State) -> None:
active_bullets: int = 0
for enemy: Enemy in s.enemies:
if enemy.bullet.y != -999:
active_bullets += 1
for enemy: Enemy in s.enemies:
enemy.y += enemy.speed
swing: u64 = cast("u64", enemy.swing) + s.frame_count
enemy.x += enemy.speed * iif(swing % 60u64 < 30u64, 1, -1)
if enemy.y > GAME_H + 50:
enemy.y = -rl.get_random_value(60, 100)
enemy.x = rl.get_random_value(0, GAME_W)
enemy.type = rl.get_random_value(1, 5)
enemy.swing = rl.get_random_value(100, 10000)
elif swing % 6u64 == 0u64 and enemy.bullet.y == -999 and enemy.y > 5 and active_bullets < ENEMY_BULLET_MAX:
enemy.bullet.x = enemy.x + 32
enemy.bullet.y = enemy.y + 32
rl.play_sound(s.assets.enemy_laser_sound)
active_bullets += 1
if enemy.bullet.y > GAME_H + 50:
enemy.bullet.x = -999
enemy.bullet.y = -999
active_bullets -= 1
elif enemy.bullet.y != -999:
enemy.bullet.y += 6
def draw_stars(s: State) -> None:
for star: Star in s.stars:
if star.r == 4:
draw_image(s.assets.meteor, star.x, star.y)
else:
rl.draw_circle(star.x, star.y, cast("float", star.r), s.assets.white)
def draw_enemies(s: State) -> None:
for enemy: Enemy in s.enemies:
draw_image(get_enemy_ship(s, enemy.type), enemy.x, enemy.y)
if enemy.bullet.y != -999:
draw_image(s.assets.enemy_laser, enemy.bullet.x, enemy.bullet.y)
def draw_player(s: State) -> None:
draw_image(s.assets.player, s.player_x, s.player_y)
for bullet: Bullet in s.player_bullets:
if bullet.y != -999:
draw_image(s.assets.player_laser, bullet.x, bullet.y)
def game_step(d: utils.Data) -> None:
s: State = cast("State", d)
ensure_assets(s)
rl.begin_drawing()
rl.clear_background(s.assets.bg)
# ----------------------------------------------
# ----------------------------------------------
if s.scene == SCENE_PLAY:
update_player(s)
update_stars(s)
update_enemies(s)
if s.frame_count % 60u64 == 0u64:
s.player_score += 1
draw_stars(s)
draw_player(s)
draw_enemies(s)
if s.scene == SCENE_TITLE:
if (s.frame_count / 50u64) % 2u64 == 0u64:
rl.draw_text("Press [enter] to start", GAME_W / 2 - 450, GAME_H / 2 - 30, 80, s.assets.white)
if rl.is_key_down(rl.KEY_ENTER):
s.scene = SCENE_PLAY
if s.scene == SCENE_GAME_OVER:
if (s.frame_count / 50u64) % 2u64 == 0u64:
rl.draw_text("Game over ", GAME_W / 2 - 200, GAME_H / 2 - 30, 80, s.assets.white)
rl.draw_text("Press [enter] to start", GAME_W / 2 - 450, GAME_H / 2 + 50, 80, s.assets.white)
if rl.is_key_down(rl.KEY_ENTER):
s.scene = SCENE_PLAY
reset_state(s)
# -----------------------------------------------
# ----------------------------------------------
rl.draw_fps(0, 0)
rl.draw_text(num.i2s(s.player_score), GAME_W - 300, 0, 64, rl.color(0, 255, 0, 255))
rl.end_drawing()
s.frame_count = s.frame_count + 1u64
def init_state() -> State:
s: State = State()
s.frame_count = 0u64
s.assets = Assets()
s.assets.loaded = False
s.stars = arrnew("Star", STAR_COUNT)
s.enemies = arrnew("Enemy", ENEMY_COUNT)
s.player_bullets = arrnew("Bullet", PLAYER_BULLETS)
random.init_random()
reset_state(s)
return s
def reset_state(s: State) -> None:
s.player_x = GAME_H / 2
s.player_y = GAME_W / 2
s.speed = 5
s.player_score = 0
for bullet: Bullet in s.player_bullets:
bullet.x = -999
bullet.y = -999
for star: Star in s.stars:
star.x = rl.get_random_value(0, GAME_W)
star.y = rl.get_random_value(0, GAME_H)
star.speed = rl.get_random_value(1, 4)
star.r = rl.get_random_value(1, 4)
for enemy: Enemy in s.enemies:
enemy.x = rl.get_random_value(0, GAME_W)
enemy.y = -rl.get_random_value(60, 100)
enemy.speed = rl.get_random_value(1, 5)
enemy.type = rl.get_random_value(1, 5)
enemy.swing = rl.get_random_value(100, 10000)
enemy.bullet.x = -999
enemy.bullet.y = -999
def ensure_assets(s: State) -> None:
if s.assets.loaded:
return
s.assets.bg = rl.color(0, 0, 0, 255)
s.assets.white = rl.color(255, 255, 255, 255)
s.assets.player = load_image("playerShip1_blue.png")
s.assets.enemy1 = load_image("shipBeige_manned.png")
s.assets.enemy2 = load_image("shipBlue_manned.png")
s.assets.enemy3 = load_image("shipPink_manned.png")
s.assets.enemy4 = load_image("shipGreen_manned.png")
s.assets.enemy5 = load_image("shipYellow_manned.png")
s.assets.meteor = load_image("meteorGrey_tiny2.png")
s.assets.enemy_laser = load_image("laserRed03.png")
s.assets.player_laser = load_image("laserBlue03.png")
s.assets.player_laser_sound = load_sound("laserRetro_000.ogg")
s.assets.enemy_laser_sound = load_sound("laserRetro_004.ogg")
s.assets.explosion_sound = load_sound("explosionCrunch_000.ogg")
s.assets.loaded = True
def del_state(current: utils.Data) -> None:
s: State = cast("State", current)
if s.assets.loaded:
rl.unload_texture(s.assets.player)
rl.unload_texture(s.assets.enemy1)
rl.unload_texture(s.assets.enemy2)
rl.unload_texture(s.assets.enemy3)
rl.unload_texture(s.assets.enemy4)
rl.unload_texture(s.assets.enemy5)
rl.unload_texture(s.assets.enemy_laser)
rl.unload_texture(s.assets.player_laser)
rl.unload_sound(s.assets.player_laser_sound)
rl.unload_sound(s.assets.enemy_laser_sound)
rl.unload_sound(s.assets.explosion_sound)
del s.enemies
del s.stars
del s.assets
del s.player_bullets
del s
def main() -> int:
s: State = init_state()
s.scene = SCENE_TITLE
rl.init_window(GAME_W, GAME_H, "Space blast")
rl.set_target_fps(60)
rl.init_audio_device()
while not rl.window_should_close():
game_step(cast("utils.Data", s))
del_state(cast("utils.Data", s))
rl.close_audio_device()
rl.close_window()
return 0
# ------------ Utilities -----------
def load_image(s: str) -> rl.Texture2D:
path: str
path = "assets/img/" + s
return rl.load_texture(path)
def load_sound(s: str) -> rl.Sound:
path: str
path = "assets/audio/" + s
return rl.load_sound(path)
def draw_image(img: rl.Texture2D, x: int, y: int) -> None:
w: int = cast("int", img.width)
h: int = cast("int", img.height)
rl.draw_texture(img, x - w / 2, y - h / 2, rl.color(255, 255, 255, 255))
def rectangle(img: rl.Texture2D, x: int, y: int) -> rl.Rectangle:
w: int = cast("int", img.width) - 2
h: int = cast("int", img.height) - 2
actual_x: float = cast("float", x - w / 2) + 1.0f
actual_y: float = cast("float", y - h / 2) + 1.0f
return rl.rectangle(actual_x, actual_y, cast("float", w), cast("float", h))
def collides(img1: rl.Texture2D, x1: int, y1: int, img2: rl.Texture2D, x2: int, y2: int) -> bool:
return rl.check_collision_recs(rectangle(img1, x1, y1), rectangle(img2, x2, y2))
def get_enemy_ship(s: State, asset: int) -> rl.Texture2D:
enemy: rl.Texture2D
if asset == 1:
enemy = s.assets.enemy1
if asset == 2:
enemy = s.assets.enemy2
if asset == 3:
enemy = s.assets.enemy3
if asset == 4:
enemy = s.assets.enemy4
if asset == 5:
enemy = s.assets.enemy5
return enemy