Gamedesign Tutorial "Snail Race!"
(ab 9 Jahren)
reinhard@finalmedia.de Sa 28. Jun 09:46:00 CEST 2025Play in Browser! (Benötigt Tastatur)
Tipp: Wenn du das Spiel im Browser startest, steht dir auch dort die ganze Entwicklungs-Plattform tic80 zur Verfügung. Drücke ESC um ins Menü zu gelangen, wähle "Close Game" mit Enter. Drücke danach nochmal ESC, um nun in den Code-Editor zu gelangen. Du kann den Code dann live ändern und mit STRG+R die Fantasy Console jederzeit wieder (mit geändertem Code) starten. Bedenke aber: Ein wirkliches Speichern des geänderten Quellcodes ist dort nicht möglich. Änderungen gehen also verloren.
Das Spielkonzept
- Es gibt einen Folge aus maximal 26 Buchstaben abcdefghijklmnopqrstuvwxyz
- Jedes Element dieser Folge wird bei Spielstart zufällig auf die Anzahl der Spieler verteilt
- Damit hat jeder Spieler "seine" exklusiven Buchstaben
- Diese liegen für jeden Spieler nun in eine Reihe vor (Pool des Spielers)
- Diese Reihe wird wiederum intern zufällig sortiert
- Der Spieler muss seine Zeichenfolge nun wieder genau in dieser nach Zufall sortierten Reihenfolge eingeben
- Bei jeder korrekten Eingabe wird der Buchstabe an der ersten Stelle im Pool des Spielers entfernt
- Drückt ein Spieler die falsche Taste, verschafft dies dem Gegner einen Vorteil, da dessen Zeichenfolge verkürzt wird
- Wer zuerst alle Buchstaben aus seinem Pool entfernt hat, gewinnt
- Es läuft ein Timer. Je schneller der Sieger war, um so höher wird seine Gesamt-Punkte-Wertung ausfallen.
Grundlagen
Präambel
Gute Computerspiele haben die nachfolgenden Eigenschaften, die beim Gamedesign von Anfang an berücksichtigt werden sollten:
- herausfordernd
- kurzweilig
- leicht verständlich
- lehrreich
- ausbaufähig
Ein Tutorial zum Gamedesign wiederum soll mehrere didaktische Ziele verfolgen.
1. Didaktisches Ziel +++ Praktik +++ definieren:
Wir möchten spielerisch Schnellschreiben auf der Tastatur trainieren.
Desweiteren soll die eigene Kreativität gefördert werden. Die Spielentwicklung soll also ausbaufähig sein und der Schüler soll eigene Ideen und Verbesserungen einbringen können. Daher darf das Spiel in der Referenzimplementierung nicht überfrachtet sein. Das Konzept soll viel Raum für eigene Ideen lassen.
Die Referenzimplementierung muss überschaubar und grafisch einfach konzipiert sein.
2. Didaktisches Ziel +++ Theorie +++ definieren:
Wissen und Methodik aus der Informatik vermitteln.
- Funktionen
- Arrays/Felder (table)
- Schleifen (for)
- Zeichenfolgen (strings)
- Zufallsfunktion (random)
- Implementierung einer Shuffle Funktion (shuffle)
Wir möchten zudem ein Verständnis von scheinbarer einfacher Intelligenz mittels der Zufallsfunktion (random) vermitteln. Ebenso vermitteln wir den Unterschied zwischen lokalen und globalen Variablen.
Bzgl. der Programmiersprache lua verwenden wir die Basisoperatoren und Stringoperatoren wie Konkatenierung mittels "..", sowie substring Funktion zum Zugriff auf einzelne Stellen der Zeichenfolge. Der Operator "#" wird benutzt, um die Länge der Zeichenfolge zu erfragen.
Zudem nutzen wir Grundlagen der Softwarearchitektur, Designentscheidung, das KISS-Prinzip (Halte es einfach) und das Überladen von Variablen zur Mehrfachbedeutung.
Das Konzept enthält darüberhinaus by Design ein EasterEgg, bzw. einen "gewollten Bug", der einem Player zufällig Vorteile verschaffen kann. Der Schüler soll dies selbst herausfinden.
Spoiler: Zwei zufällig aufeinanderfolgende gleiche Buchstaben in der Zeichenkette geben dem jeweiligen Spieler einen Vorteil, da er sie "mit einem Haps" in einem Zug wegfrühstückt, da das Timing 60fps läuft und key autorepeat der Plattform greift.
Implementierung
Designvorgaben
## Verbindliche Designvorgaben zur Implementierung - Es soll eine Maximalanzahl von 4096 Punkten geben. - Es soll mind. 3 Spieler geben - Es soll von diesen 3 Spielern mind. einen Computergegner geben - Es soll mind. 5 Schwierigkeitsgrade geben, die den Computergegner besser oder schlechter machen - Jeder Buchstabe aus dem Pool des Spielers soll für den Spieler exakt 3 mal vorkommen - Es soll einen Gesamtpool aus nur 24 Buchstaben geben - Die Spieler sollen optisch gut unterscheidbar sein
Plattformvorgaben
Wir programmieren in der Programmiersprache "lua" auf der Plattform "TIC-80", einer Fantasy Konsole.
Diese Plattform/IDE bringt auch einen integrierten Pixel-Grafikeditor mit, damit der Schüler die Protagonisten grafisch selbst gestalten kann. Und so ein spielerischer Einstieg gegeben ist, bevor man sich dem Quellcode widmet.
## Verbindliche Plattformvorgaben zur Implementierung - Grafik der Spieler sollen als 8x8 Pixelgrafik in den tile Slots 49,50,51 definiert werden. (Wir halten uns die echten Sprite Slots frei für spätere Implementierung von animierten Sprites) - Zur Gestaltung kann darf der integrierte Editor verwendet werden. Beispiel:

## Grafik der Referenzimplementierung Spieler 1: Tile 49 8x8 Pixel Spieler 2: Tile 50 8x8 Pixel Spieler 3: Tile 51 8x8 Pixel
Referenzimplementierung
## Der Quellcode meiner Referenzimplementierung
-- title: snail race
-- author: reinhard@finalmedia.de
-- desc: tiny snail race keyboard typing trainer
-- site: finalmedia.de
-- license: CC-BY-SA
-- version: 0.1
-- script: lua
-- globale variablen definieren
t=0
y=56
x=16
maxscore=4096
handicap=50
level=2
step=6
a1="qweasdyx"
a2="rtzfghvb"
a3="uiopjklm"
gameover=1
winner=0
function shuffle(str)
local chars = {}
for i=1, #str do
chars [i] =str:sub(i,i)
end
for i = #chars, 2, -1 do
local j = math.random(i)
chars[i], chars[j] = chars[j], chars[i]
end
return table.concat(chars)
end
function newgame()
gameover=0
score=maxscore
winner=0
t=0
x1=x
x2=x
x3=x
s1=""
s2=""
s3=""
for i=0,level,1 do
s1=s1..shuffle(a1)
s2=s2..shuffle(a2)
s3=s3..shuffle(a3)
end
end
function winspr(player)
winner=player
gameover=t
end
function BOOT()
newgame()
end
function TIC()
-- hintergrund farbe setzen
cls(0)
-- alle drei schnecken zeichnen
spr(49,x1,y,0,2,0,0,1,1)
spr(50,x2,y+10,0,2,0,0,1,1)
spr(51,x3,y+20,0,2,0,0,1,1)
-- titel und anleitung zeichnen
print("SNAIL RACE!",1,9,12)
print("Hold enter to generate new seed.",1,18,12)
print("Press your current letter key to eat.",1,27,12)
print("Press 0-5 to choose handicap.",1,36,12)
print("Computer player is green.",1,45,12)
-- jede essenspur aus buchstaben zeichnen
print(s1,x1+20,y+10)
print(s2,x2+20,y+20)
print(s3,x3+20,y+30)
-- entertaste = neues spiel
if (key(50)) then newgame() end
-- zifferntasten schwierigkeitsgrad
if (key(27)) then handicap=99 end -- easy
if (key(28)) then handicap=50 end -- default
if (key(29)) then handicap=40 end
if (key(30)) then handicap=30 end
if (key(31)) then handicap=20 end
if (key(32)) then handicap=10 end -- insane :)
-- siegergrafik anzeigen, falls spiel gewonnen
if (winner>0) then
spr(winner,90,66,0,6,0,0,1,1)
print("WINNER! "..score.. " Points",65,120,12)
end
-- aktuellen ziel-buchstaben jedes players finden
c1=s1:sub(1,1)
c2=s2:sub(1,1)
c3=s3:sub(1,1)
-- solange noch niemand gewonnen hat
-- buchstaben eingaben zulassen
if (gameover<1) then
for keycode = 1,26 do
if key(keycode) then
if ((keycode+96)==c1:byte()) then s1=s1:sub(2); x1=x1+step end
if ((keycode+96)==c2:byte()) then s2=s2:sub(2); x2=x2+step end
if ((keycode+96)==c3:byte()) then s3=s3:sub(2); x3=x3+step end
end
end
end
-- aktuelle score ausgeben
print(score,1,1)
-- aktuelles handicap ausgeben
print(handicap,226,1)
-- aktuelle buchstaben ausgeben
print(c1,1,128,10)
print(c2,10,128,4)
print(c3,20,128,5)
-- aktuelle restanzahl ausgeben
print(#s1,1,y+10,10)
print(#s2,1,y+20,4)
print(#s3,1,y+30,5)
-- gewinner, wenn all buchstaben gegessen
if (#s1<1) then winspr(49) end
if (#s2<1) then winspr(50) end
if (#s3<1) then winspr(51) end
-- computer player
if (t>handicap and math.random(handicap)==1 and gameover<1) then
s3=s3:sub(2)
x3=x3+step
end
-- die zeit vergeht und highscore schwindet
if (gameover<1) then
t=t+1
if (t < maxscore) then score=score-1 end
end
end
Weiterführendes
Ausbaumöglichkeiten
Modi implementieren
Chaos Mode
Möglichkeit eines zusätzlichen "Chaosmode": Für Tastatureingabe der realen Player (statt linker und rechter Bereich) einfach aus dem Pool aller Buchstaben, reihum an alle Player einen Buchstaben in dessen pool vergeben. Grafikdesign: z.B. eine Tastatur mit vielen Händen und Fingern über Kreuz. Realisierung ist hier recht einfach, da nur die player pools anders befüllt werden. Das erhöht den Spielspaß bei 3-4 menschlichen Spielern, die dann z.B. alle auf einer Tastatur ihre jeweiligen Buchstaben finden müssen und sich dabei die Finger verknoten. Natürlich können jedoch an einen Rechner auch mehrere Tastaturen angeschlossen werden.
Demo Mode
Da wir in den Vorgaben bewusst nicht gefordert haben, dass es mindestens einen menschlichen Spieler geben muss, ist es denkbar, nur Computergegener gegeneinander antreten zu lassen. Das ist dann als ein Demo-Mode zu verstehen, in dem man sich zurücklegen kann und nur zuschaut. Beachte bei der Implementierung eines solchen Demo-Modes, dass jeder Computergegner sein eigenes Zufalls-Timing haben muss und idealerweise auch leichte Schwankungen im jeweiligen Handicap berücksichtig werden! (addition und subtraktion eines kleinen zufallswertes)
Animationen und Grafik
die Sprites können animiert werden, sodass die Schnecke z.B. zu fressen scheint. Zudem kann im Ziel für jede Schnecke eine Häuschen liegen, welches sie dann nach getaner Arbeit aufsetzen kann, um zu ruhen.
Schriftarten (Fonts)
Statt der Standard Systemschrift können Grafikfonts verwendet werden, die neben dem Buchstaben z.B. Früchte zeigen etc. Die Buchstabenspur kann auf diese weise sehr einfach grafisch aufgewertet werden, ohne die Implementierung stark zu verändern. Zudem sind Font-Wechsel möglich. Idealerweise sollten auch nur Schriftarten mit fester Lauflänge verwendet werden, um Renderjumps zu vermeiden.
Multiplayer Ausbau
Mehr als 3 Spieler realisieren. z.B. bis zu 8 pieler, davon dann auch mehrere Computergegner und wählbar (auch via Tables und Object).
Persistente Upgrades
Ggf. auch die Sprites der Spieler änderbar gestalten, sodass sie in jeder gewonnenen Runde "erfolgreicher" aussehen, z.B. Sternchen, Sticker oder Pokale erhalten. Dazu z.B. unterhalb neue Sprites einführen und dann ranking-Variable implementieren, die den sprite index des players shifted.
Sounds
Die TIC-80 Konsole ermöglicht auch die einfache Gestaltung von Sound und Musik für das Spiel, um die Lebendigkeit zu erhöhen.
Codeoptimierung
Durch Einsatz von Tables (Arrays), statt einer dedizierten Variable pro Spieler
Fragen und Antworten
Frage: Wie schnell wächst der Wert der Variable t?
Antwort: Da tic80 mit einer festen Rendering Framerate von 60 fps arbeitet, also die Hauptfunktion TIC() 60 mal pro Sekunde aufgerufen wird,
wächst also t auch pro Sekunde um den Wert 60. Dieser Wert kann aber auf Millisekunden-Ebene schwanken, da hier keine Echtzeitverarbeitung
stattfindet.
Frage: Wie kann man die Fallunterscheidung bzgl. der maxscore am Ende eleganter und ohne Vergleich mit t oder maxscore schreiben?
Antwort: if(score>1) then score=score-1 end
Frage: Wozu braucht es überhaupt die Zeitvariable (tick) t?
Antwort: Primär Zum verzögerten Einstieg der Computergegner nach handicap, weil der computer sonst immer einen Vorteil hat, bevor der menschliche Spieler überhaupt die Zeichenfolge wahrgenommen haben kann.
Frage: Wie ist die "Intelligenz" des Computergegners realisiert?
Antwort: Bedingt durch das gewählte Handicap wird eine Zufallszahl in einem gewissen kleineren oder größeren Bereich gewählt.
Der Computergegner darf immer nur dann voranschreiten, wenn diese Zufallszahl eine 1 ist.
Wird also der mögliche Wertebereich insgesamt vergrößert, so sinkt automatisch die Wahrscheinlichkeit eines Treffers.
Damit spielt der Computergegner also bei einem hohen Handicap random(99) und damit Zahlen zwischen 0 und 99, letztlich schlechter,
als bei einem niedrigen Handicap random(10) und damit Zahlen zwischen 0 und 10. Das Handicap verlängert also unterm Strich
elegant die zeitliche Dauer vor dem nächsten Zug des Computergegners.
Frage: Wie entfernt man die erste Stelle einer Zeichenfolge?
Antwort: In tic80 lua am elegantesten mittels string=string:sub(2), was bedeutet dass der string erst
ab stelle 2 beginnt, also der Rest übrig bleibt. Und diesen Rest weisen wir dem String wieder selbst zu.
So ist es auch in der Referenzimplementierung zu sehen.
Frage: Erkläre die Zeile (keycode+96)==c1:byte()
Antwort: in tic80 bezieht sich die funktion key(1) ja bereits auf buchstabe A, weil tic80 diese
Buchstabenfolge schon so vorgibt. Die systemseitigen zeichenfolgen in lua sind jedoch ASCII Zeichenfolgen
und in der ASCII Tabelle starten die Kleinbuchstaben an der Dezimalstelle 97 (hexadezimal 0x61).
Da angegebene offset setzt also den tic80 keycode auf das ascii byte um und vergleicht.
Damit wird die Abfrage realisiert, ob die gedrückte Taste dem ASCII Zeichen des ersten Zeichens in der
Zeichenfolge des jeweiligen Spielers entspricht. In Diesem Fall für Spieler 1.
Frage: Erkläre die definierte Funktion winspr()
Antwort: wir unterbrechen den gameloop, indem wir die gameover variable auf die aktuelle zeit setzen.
es würde hier genügen, die variable auf 1 zu setzen. wir erhalten uns aber so auch den wert der Gesamtdauer
für spätere Erweiterungen. Mittels der spr() funktion wiederum stellen wir den Gewinner dar,
allerdings nun 4 fach vergrößert. Der übergabe Wert für die winspr() Funktion ist folglich der
Dezimalwert der player grafik, also tiles 49,50,51 in unserer Referenzimplementierung.
Frage: Erkläre die for Schleife in der Funktion newgame()
Antwort: Die Variable level hat den Wert 3. Die Variable i nutzen wir nur als counter von 1 bis 3,
womit letztlich die jeweiligen Zeichenfolge-Strings der Spieler dreimalig verlängert werden,
jeweils um eine neu gewürfelte sequenz aus dem erlaubten zeichenpool des jeweiligen spielers.
Bonusfragen
Frage: Unter welchen Bedingungen tritt in der Referenzimplementierung bei sehr langer Spielzeit hypothetisch ein Überlauf eines kritischen Wertebereichs ein?
Antwort: t könnte hypothetisch überlaufen, wenn nur menschliche Spieler am Start wären und diese mehr als ein Jahr keinerlei Spieleingaben tätigen würden.
Durch die Vorgabe, dass es jedoch mind. einen Computergegener geben muss, tritt dieser Fall nicht, bzw. nur mit extrem niedriger Wahrscheinlichkeit ein.
Der Computergegner müsste für den maxint32 überlauf ganze 24 mal über eine Zeit von 2.147.483.647/60 Sekunden = 35791394 Sekunden = 9942 Stunden = 414 Tage
lang keine zufällige 1 aus seinem sehr geringer handicap Bereich zwischen 1 und 50 bekommen haben. Was sehr sehr unwahrscheinlich ist.
t wird ja nicht weiter inkrementiert, sobald ein Spieler gewonnen hat. Im Falle einer Manipulation des Pseudo-Zufallszahlengenators,
und dass TIC-80 für diesen nie die 1 returned, und somit lange Zeit niemand gewinnt, könnte also nach 414 Tagen ein Überlauf stattfinden
und das Spiel abstürzen. Vorherige Abstürze aus anderen Gründen sind ggf. wahrscheinlicher ;)
Downloads (Referenzimplementierung)
Native Binaries
Quellcode
sneckweg.tic (Tic80)