Yeah, me

Foto: Uwe Pirr

Informatik


previous Page home toc

Programmieren in HyperTalk, Teil VII

Abb.: 1: Im Kerker des Grauens

Es ist dunkel um Dich herum. Die Hälfte der Höhle hast Du schon erforscht. Da ein fürchterlicher Schrei. Wieder greift Dich einer der Trolle, die dieses Höhlenlabyrinth bevölkern, an. Nur mit Mühe kannst ihn mit einem Schwerthieb abwehren. Und noch immer hast Du nicht alle Schätze dieser Höhle gefunden. Und wo der verdammte Ausgang ist, weißt Du auch noch nicht. Dabei geht Deine Lebensenergie langsam zu Ende ... So oder ähnlich geht es in vielen "Höhlen-Spielen" (Dungeon Games) zu. Ich möchte Ihnen - gegen Ende dieses kleinen HyperTalk-Kurses - zeigen, daß man solch ein Spiel auch in HyperCard erstellen kann. Wir stoßen dabei aber ziemlich an die Grenzen der Leistungsfähigkeit von HyperCard. Unser Spiel demonstriert Ihnen daher nur die Prinzipien eines solchen Spieles. Auf meinem PowerMac läuft das Spiel recht flott, auf meinem Duo 2300 mit gerade noch erträglicher Geschwindigkeit ab.

Die Spiel-Idee

Das Spielfeld ist in 16 x 10 Felder von je 32 Pixel Breite unterteilt. Der Spieler sieht nur die acht Felder um sich herum, alle anderen sind schwarz. Erst wenn er die anderen Felder besucht hat, werden sie für ihn sichtbar. Jeder Level besteht aus einigen Räumen, die miteinander verbunden sind (sich auch überschneiden können - das gibt dem Labyrinth einen verwinkelten Charakter). Auf dem Spielfeld sind Schätze verteilt, die es zu sammeln gilt. Daneben sind die Höhlen von Trollen bewohnt, die unseren Helden angreifen und ihn verfolgen, wenn er nahe genug an sie herangekommen ist, daß sie ihn "riechen". Wenn unser Hero die Trolle angreift oder von ihnen angegriffen wird, besteht eine 50%ige Wahrscheinlichkeit, daß er sie besiegt. Verliert er, bekommt er einen Lebenspunkt abgezogen.

Das Spiel endet, wenn der Spieler keine Lebenspunkte mehr besitzt (oder wenn der Panic-Button gedrückt wird). Hat er dagegen "lebend" den Ausgang erreicht, erzeugt unser Programm ein neues zufälliges Labyrinth, indem sich unser Held erneut bewähren muß.

Die Grafik

Wer den Kurs bis hierhin verfolgt hat, kann sich sicher denken, warum die einzelnen Spielfelder 32 x 32 Pixel groß sind. Das ist genau die Größe eines Icons für einen Button. Jedes Spielfeld ist daher als Button realisiert, daß bei entsprechenden Spielzügen mit
	set the icon of cd button <Name> to <Icon>
sein Aussehen verändert. Die "Bewegungen" sind daher etwas sprunghaft. Außerdem habe ich für jeden Akteur nur ein Icon vorgesehen. Wenn Sie das Spiel etwas ausbauen wollen, können Sie weitere Icons erzeugen, die die Akteure in ihrer jeweiligen Bewegungsrichtung zeigen. Abb. 2 zeigt die verwendeten Icons (bis auf das blackTile, das ist einfach nur ein schwarzes Feld).:

Abb 2: Die verwendeten Icons

Ich habe die Icons im Icon-Editor von ResEdit erstellt, der im Gegensatz zum Icon-Editor von HyperCard Patterns (wie den Fußboden oder die Mauer) zuläßt. Nachdem sie in die Resource-Fork unseres Stacks kopiert wurden, wurden sie mit folgender Codesequenz ansprechbar gemacht:
	put 128 into kPlayerTile
put 129 into kEmptyTile
put 130 into kTrollTile
put 131 into kMoneyTile
put 132 into kWallTile
put 133 into kExitTile
put 134 into kBlackTile
Aus Performance-Gründen habe ich diesmal darauf verzichtet, Konstanten als Funktionen zu deklarieren. Um sie wenigstens ein bißchen sicherer zu machen, habe ich sie nach guter alter C-Sitte mit einem kleinen, vorangestellten "k" versehen. Globale Container sind ebenfalls mit einem vorangestellten "g" kenntlich gemacht.

Der Sound

Ein Spiel ohne Sound ist nichts. In unserem Stack werden folgende Geräusche verwendet:
	IntroSound: Geheimnisvolle Musik zu Beginn des Spiels
	ExitSound: Triumphmarsch, wenn der Level verlassen wird
	GetMoney: Klirrend wird das Geld eingesackt
	HeroAttacks: Schwerter klirren beim Angriff unseres Helden
	MonsterAttacks: Ein Urschrei, den das Monster ausstößt.
	MonsterDied: Trolle sterben ziemlich geräuschvoll
	HeroDied: Und auch bei Helden geht der Tod nicht geräuschlos ab
Alle diese Geräusche habe ich von verschiedenen PD- und Shareware-CD's "geborgt" und ebenfalls mit Hilfe von ResEdit in den Stack kopiert.

Der Stack wird erstellt

Es ist ziemlich umständlich, 160 Buttons (16 x 10) per Hand zu erstellen und exakt zu positionieren. Daher lassen wir das HyperTalk erledigen. In einer doppelten Schleife berechnen wir mit
   put (i*kTileSize) - (kTileSize DIV 2) into item 1 of tileLoc
   put (j*kTileSize) - (kTileSize DIV 2) into item 2 of tileLoc
die Koordinaten der einzelnen Felder. Anschließend rufen wir per Programm dein Menüeintrag New Button auf, setzen ihn auf seine richtige Größe, legen seinen Stil (transparent) fest und positionieren ihn. Mit
   set the name of card Button "New Button" to i & ", " & j
geben wir ihm auch gleich einen Namen, der seinen Koordinaten entspricht. Hier wird wieder der Umstand ausgenutzt, daß HyperTalk nur den Datentyp Text kennt. So können wir jedes Spielfeld ohne große Konvertierung über seine Koordinaten ansprechen.

Ich habe im Text die Befehle lock Screen und unlock Screen auskommentiert. Sie beschleunigen den Bildaufbau beträchtlich (im eigentlichen Spielprogramm wird daher von ihnen Gebrauch gemacht), hier ist es jedoch ganz witzig, zu sehen, wie das Programm die Buttons nacheinander erstellt.

Da die 160 Buttons den Standardstapel nicht komplett füllen, habe ich den unteren Rand (wo auch der Panic-Knopf liegt) einfach mit Hilfe des Zeichentools schwarz eingefärbt.

Das Spielprogramm

Das Spielprogramm besteht im Prinzip aus drei großen Handles, die sich nacheinander aufrufen (Abb. 3). Zuerst fängt ein mouseUp alle Klicks ein, die nicht vom Panic-Button absorbiert werden. Dies prüft dann auch sofort, in welchem Button der Klick stattgefunden hat und weist diesen Button-Namen als Koordinate an das Handle movePlayer weiter.

Abb 3: Flowchart unseres HyperTalk-Programmes
Klick in das Bild bringt eine Vergrößerung.

Hier wird erst einmal abgeprüft, ob das angeklickte Feld gültig ist, ob es leer ist, ob dort ein Schatz liegt oder ein Troll lauert usw. Je nach Zustand des Feldes werden weitere Aktionen ausgelöst. Wieder einmal macht sich hier das Fehlen einer case-Anweisung in HyperTalk bemerkbar. Die if then - else-Schachtelungen sind leider etwas unübersichtlich geraten. Sind alle notwendigen Spielzüge ausgeführt und der Held hat sie überlebt, ist das Handle moveTroll an der Reihe.

Dies fragt einfach alle Spielfelder ab, ob sich ein Troll auf ihm befindet. Wenn nicht, wird er bewegt. Hier entsteht eine Schwierigkeit. Da ein Troll von hier auf ein noch nicht abgefragtes Feld bewegt werden kann, wird er eventuell mehrmals bewegt. Um dem vorzubeugen, wird der neue Trollplatz erst einmal mit der Konstanten kTempTroll belegt, die von dieser Schleife über das Spielfeld ignoriert wird. Erst in einer zweiten Schleife werden alle "temporären" Trolle in "echte" Trolle zurückverwandelt.

Auch dieser Programmteil prüft ab, ob unser Hero einen eventuellen Angriff der Trolle überlebt hat und beendet gegebenenfalls das Spiel. Dabei ist es fast wie im "richtigen Krieg". Das Programm läßt es ohne weiteres zu, daß mehrere Trolle (quasi) gleichzeitig angreifen, oder das ein Angriff unseres Helden auf einen Troll erfolgt, der seinerseits beschlossen hat, unseren Spieler anzugreifen. Während nun bei einem erfolgreichen Angriff des Spielers dieser seinen Platz auf dem des Trolls einnimmt, bleibt er bei einer erfolgreichen Abwehr eines Trollangriffs auf seinem Spielfeld. Wenn nun beides gleichzeitig erfolgt, kann es sein, daß der Zufallszahlengenerator beide Angriffe als mißlungen bezeichnet. Wenn nun unser Held dabei auch noch überlebt, ist der Troll zwar tot (Trolle haben - im Gegensatz zum Spieler - nur ein Leben), trotzdem bleibt er auf seinem Platz, obwohl wir ihm Angriff befohlen haben. Dieses Verhalten mag zwar zu verwirren, ist aber spielemäßig völlig korrekt.

Ein neues Labyrinth wird erzeugt

Abb 4: Im Halbdunkel

Jedesmal, wenn der Spieler einen Level erfolgreich verlassen hat, wird ein neues zufälliges Labyrinth erzeugt, damit das Spiel nicht zu langweilig wird. Die Idee folgt einem Algorithmus aus [1], wurde aber schon, wie Dewdney berichtete [2] in den ersten, noch textorientierten Dungeon-Games wie Rogue oder NetHack verwendet.

Dabei wird zuerst die Anzahl der Räume ausgewürfelt und deren obere Kante und Größe bestimmt. Dann werden diese Räume gezeichnet. In den meisten Fällen überlappen sie sich auf so einem kleinen Spielfeld wie dem unseren und sind daher schon miteinander verbunden. (Daher ist es etwas vermessen, von einem Labyrinth zu sprechen.) Unser Programm prüft dies jedoch nicht ab, sondern zeichnet einfach noch einen Weg von jedem Raum zum nächsten ein. Dabei wird zufällig ein Punkt im ersten Raum ausgewählt und ein Punkt im nächsten. Dann wird solange geradeaus gegangen (entweder von rechts nach links oder umgekehrt) bis die x-Koordinate des zweiten Raums erreicht ist. Dann wird in die vertikale gewechselt und das Programm wählt einen Weg auf- oder abwärts, bis auch die y-Koordinate erreicht wurde. So können, wie Abb. 4 zeigt, auch einmal Mauerstücke im Raum stehenbleiben und in den meisten Fällen werden die Räume verwinkelter und interessanter, als sie es ohne diesen zusätzlichen Weg wären.

Das Erzeugen dieses neuen Labyrinthes ist für HyperCard schon ziemlich aufwendig und Sie sollten eine entsprechend lange Titel- und Übergangsmusik auswählen, um beim Spieler keine Ungeduld aufkommen zu lassen.

Die Hilfsroutinen

Neben der Funktion numberOfRooms, die nur eine Zufallszahl zwischen drei und sechs zurückgibt, gibt es noch zwei größere Unterprogramme: showAroundPlayer prüft nach, ob die acht Spielfelder um den Spieler herum noch unsichtbar sind und macht sie gegebenenfalls sichtbar. drawTile übernimmt die eigentliche Zeichenfunktion der Spielfelder und ist vor allem deswegen ausgelagert, weil es nicht zwingend ist, die Spielfelder als Icons von Buttons darzustellen. Sie sind z.B. auch als PICT-Ressourcen vorstellbar. In diesem Fall könnten sie mit Hilfe der Color-Tools von HyperCard 2.3 auch in Farbe dargestellt werden. Ich gebe zu, ich habe dies getestet, weil es meine ursprüngliche Intention zu diesem Artikel war, auf die Farbfähigkeit von HyperCard einzugehen. Die Spielzüge wurden dann aber selbst auf meinem PowerMac unerträglich langsam, von der Initialisierung des Spielfeldes wollen wir gar nicht erst reden. Mein bisheriger Eindruck, daß Farbe in HyperTalk-Programmen momentan noch nicht einsetzbar ist, hat sich wieder bestätigt. (Dies gilt nicht in gleichem Maße für HyperCard-Anwendungen: Wenn die Farbe direkt in den Stack gesetzt wird und nicht oder nur in geringem Maße von einem HyperTalk-Script beeinflußt wird, sind durchaus ansprechbare Ergebnisse zu erzielen.) Wir Programmierer müssen daher (wieder einmal) auf HyperCard 3.0 hoffen.

Optimierung

Wenn Sie selber solch ein oder ein ähnliches Programm entwickeln wollen, sollten Sie, um Fehler zu finden, erst einmal alle strukturierten Variablen (Arrays und Records) in Felder schreiben. In unserem Spiel sind das folgende:
  gTileArray
gTileKnown
gPlayer
gTileArray enthält die einzelne Spielfeldbelegung, gTileKnown "weiß", ob ein Feld sichtbar oder unsichtbar ist und gPlayer enthält die augenblicklichen Koordinaten des Spielers und seine Lebenspunkte. (Hier können Sie bei einer entsprechenden Erweiterung des Spieles zum Beispiel auch Pluspunkte für gesammelte Schätze oder mitgenommene Gegenstände usw. aufnehmen.) Nach erfolgter - hoffentlich erfolgreicher - Fehlersuche sollten diese Felder jedoch in globale Container umgewandelt werden, um die Geschwindigkeit des Programms zu erhöhen. Und achten sie bitte darauf, daß dann auch alle Felder wieder von Ihrer Karte gelöscht werden. Das Schreiben und Suchen von Feldern kostet HyperTalk unheimlich viel Zeit. Und da wir hier an die Grenze der Leistungsfähigkeit vorgestoßen sind, müssen wir leider Programmiersicherheit gegen Schnelligkeit eintauschen. Ähnliches gilt auch für das Array kTileSize, das die Richtungskoordinaten eines Zuges enthält. Unter normalen Umständen hätte ich auch diese Angaben in ein Feld geschrieben, statt sie mühselig mit put-Anweisungen im Quellcode zu initialisieren. Aber auch hierbei ist der Geschwindigkeitsvorteil nicht zu unterschätzen.

Zu guter Letzt werden alle Bildschirmausgaben vorgenommen, wenn lockScreen gesetzt ist, so daß ein Redraw erst stattfindet, wenn alle Änderungen HyperCard bekannt sind. Auch dies beschleunigt HyperCard signifikant. Um dem Benutzer dennoch mitzuteilen, daß etwas passiert und sein Rechner nicht abgestürzt ist, mach ich ihn mit

   set cursor to watch
(dieser Befehl zeigt den Cursor als Uhr) darauf aufmerksam, daß er noch warten möchte. Mit
   set cursor to finger
wird er wieder auf seine normale Form (browse tool) zurückgesetzt.

An dieser Stelle ist anzumerken, daß viele HyperTalk-Manuals dieses falsch behandeln. Sie geben an, daß der Befehl set cursor to hand hieße. Dieser Befehl macht den Cursor aber zu einer flachen Hand, wie sie beim Verschieben von Bildern gezeigt wird und nicht zum Zeigefinger.

Erweiterungen

In der abgedruckten Version kann der Spieler in den meisten Fällen den Trollen entkommen und wird so schnell langweilig. Daher kann auch dieses Programm von Ihnen natürlich ausgebaut werden. Im schon erwähnten Artikel von Dewdney finden Sie viele Anregungen. Andere Monster sind denkbar, die unterschiedliche Verhalten an den Tag legen (Urige Ungetüme, Greifen oder ähnliches). Ihr Held kann Zaubersprüche finden oder Amulette. Herumliegende Nahrung kann seine Lebenspunkte aufbessern, Zaubertränke ihm, wie bei Asterix und Obelix, ungeahnte Kräfte verleihen. Versteckte oder sichtbare Falltüren können ihn Level überspringen lassen oder auch in gefährliche Situation bringen. Er kann Waffen und Rüstungen finden oder Tarnkappen, die ihn unsichtbar gegenüber seinen Gegnern machen (außer den Trollen natürlich, die haben bekanntlich einen unübertrefflichen Riecher für Menschenfleisch). Zwei Dinge sind bei solchen Erweiterungen zu beachten:

Sie müssen die Variable gPlayer entsprechend erweitern, um Punktstände, Nahrungsvorräte und Rüstungen oder Waffen mitzuführen und

sie sollten für jeden Level eine eigene Karte anlegen, auf dem die level-spezifischen Programmteile zu finden sind. Falls Sie andere Hintergründe benötigen, sollten Sie diese Level sogar jeweils in eigene Stacks auslagern. Ansonsten wird Ihr Spiel zu langsam und das Spielen macht keinen Spaß mehr.

Viele andere Spiele bauen auf ähnlichen Rastern auf. So könnten Sie sich auch einmal an das bekannte MineSweeper, an das Kistenschiebespiel Sokoban oder an Othello versuchen. Viel Spaß!

Damit Sie das Programm nachverfolgen können, hier das eigentliche Programmlisting und das Listing des Panic-Buttons.

Wie weiter?

In dieser letzten Folge meines kleinen HyperTalk-Kurses wollte ich zeigen, daß durchaus ansprechende Programme mit HyperTalk programmiert werden können. (Na gut, daß mit den Bildern der Icons übe ich noch - ich bin nun mal kein Grafiker. Aber sonst ...). Jetzt liegt alles bei Ihnen - programmieren Sie in HyperTalk und haben Sie genausoviel Spaß dabei, wie ich ihn bei der Abfassung dieses Kurses hatte...

© Jörg Kantel, 1997

Literaturliste

[1] Jamie McCornack, Ingemar Ragnemalm, Paul Celestin et al.: Tricks of the Mac Game Programming Gurus, Indianapolis, Indiana (Hayden Books) 1995

[2] A.K. Dewdney: Im Kerker des Verderbens, in: Ingo Diemer (Hg.): Computer Kurzweil, Reihe: Spektrum der Wissenschaft: Verständliche Forschung, Heidelberg 1988


previous Page home toc

This pages need no frames
This site is edited with Radio UserLand, the first personal Web Application server for Windows and Macintosh. © 1996 - 2001 by Jörg Kantel
Last modified: JK, 04.09.2001; 20:33:33 Uhr
email:joerg@kantel.de
This page is best viewed with a computer and a monitor

Site Meter