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.