Kiste
DE

Sternjäger

An arcade space shooter — enemy waves, power-ups, effects and a high-score list. About 700 lines of Kiste in a single file, no game engine.

Sternjäger gameplay

Download the game

Download, unzip, run — you get exactly what you see in the video. The full source (sternjaeger.ki) is included in every package.

How it’s built

Sternjäger is a pure Kiste program. It uses only the built-in modules gui (windows, drawing, keys), klang (sound and music), liste, zufall and datei — no external game engine. Here are the most interesting parts. The code is German, because Kiste is.

1 · The game tick

A single timer drives everything: 60 times per second (every 16 ms) the world moves and is redrawn. A modus value decides whether the menu, the game, the pause or the game-over screen is running.

nimm uhr = gui.bei_zeit(16, funktion(t) {
    sterne_bewegen()
    partikel_bewegen()
    wenn flash > 0 { flash = flash - 1 }
    wenn modus == 1 { spiel_logik() }   // only run logic while playing
    zeichne()                           // but always redraw
})

2 · Everything is parallel lists

Instead of objects, Sternjäger keeps one list per property — enemy number j has its position in ex[j]/ey[j], its type in et[j], and so on. Simple and fast.

// Every enemy property is its own parallel list.
// Create empty lists with liste.gefüllt(0, …) so the
// compiler knows the element type.
nimm ex = liste.gefüllt(0, 0)    // x position
nimm ey = liste.gefüllt(0, 0)    // y position
nimm et = liste.gefüllt(0, 0)    // type 0..4
nimm eh = liste.gefüllt(0, 0)    // hit points
nimm ealive = liste.gefüllt(0, 0)

3 · Enemies with their own behaviour

Five enemy types. The longer you survive (the higher the welle, the wave), the tougher the types that appear — and the more often.

// Higher waves bring in tougher enemies.
nimm r = zufall.ganz(0, 99)
nimm t = 0
wenn welle >= 4 und r < 6 + welle {
    t = 4                        // Tank (from wave 5)
} sonstwenn welle >= 2 und r < 30 {
    t = 3                        // Chaser (from wave 3)
} sonstwenn r < 22 + schwer {
    t = 2                        // Shooter
} sonstwenn r < 60 {
    t = 1                        // Zig-zag
} sonst {
    t = 0                        // Drifter
}

Each type behaves differently. The Chaser, for instance, checks where the player is every tick and steers sideways toward them:

// Type 3 (Chaser) steers sideways toward the player each tick
wenn t == 3 {
    wenn px > wert.als_ganz(ex[j]) { evx[j] = 2 } sonst { evx[j] = -2 }
}

4 · Collectible items and power-ups

Destroyed enemies sometimes drop an item. Touch it with your ship and sammle kicks in: the bomb clears the screen, the shield makes you briefly invulnerable, the double shot fires two streams. Power-ups are just counters that tick down each frame.

funktion sammle(typ) {
    spiele_ton(power_s)
    wenn typ == 0 { zünde_bombe() }         // clears every visible enemy
    sonstwenn typ == 1 { schild = 300 }     // ~5 s invulnerable
    sonst { doppel = 480 }                  // ~8 s double shot
}

funktion spieler_getroffen() {
    wenn schild > 0 { gib }                 // shield absorbs the hit
    leben = leben - 1
    flash = 8
    wenn leben <= 0 { game_over() }
}

5 · Drawing with rectangles and circles

There are no image files for the ship, enemies or effects — everything is assembled each frame from gui.rechteck, gui.kreis and gui.text_an. For neatly centred text, gui.text_breite measures the real pixel width.

// Real centering via gui.text_breite (measures pixel width)
funktion text_mitte(cx, y, txt, farbe, groesse) {
    gui.text_an(bild, cx - gui.text_breite(txt, groesse) div 2, y, txt, farbe, groesse)
}

// The ship — just rectangles and one circle
gui.rechteck(bild, px - 3, SPIELER_Y - 24, 6, 30, "#bae6fd")
gui.rechteck(bild, px - 18, SPIELER_Y + 2, 36, 8, "#38bdf8")
gui.kreis(bild, px, SPIELER_Y - 14, 4, "#e0f2fe")

6 · Menu, logo and quitting

The high-score list is saved as a small text file and loaded at start. A custom logo replaces the default icon, and “Quit” simply closes the main window.

// Custom logo in the title bar and taskbar
gui.setze_app_icon("sternjaeger-logo.png")

// "Quit" simply closes the main window
funktion beende_spiel() {
    gui.fenster_schließe(fenster)
}

That’s the whole recipe: one timer, a few lists, rectangles and circles. The complete source ships with every download above — change the enemies, invent new power-ups, make it your own game.