Read or Die: Über RSS-Feeds

Stellt euch vor, jeden Tag legte euch ein Diener alle eure Lieblingszeitungen, -magazine und Kopien aus anderen interessanten Publikationen auf den Tisch. Obwohl sich darunter viele hochklassige und unterhaltsame Texte befinden, werdet ihr niemals die Zeit und Lust finden, sie alle zu lesen oder auch nur zu überfliegen. Also denkt ihr euch: Kein Problem, sie werden morgen oder nächste Woche immer noch genauso gut sein wie heute. Und das stimmt, doch morgen und nächste Woche bringt euch der gewissenhafte Diener die nächsten Ausgaben, und die Papierstapel wachsen immer weiter in die Höhe. Vernünftig, wie ihr seid, entschließt ihr euch, den Realitäten ins Auge zu sehen, die Türme einzureißen und zum Altpapier zu werfen. So verfahrt ihr nun regelmäßig, nachdem ihr ein paar Artikel gelesen habt, die eure Aufmerksamkeit gewannen.

Read or Die

Ihr bemerkt, dass ihr diese Artikel allmählich nach seltsamen Kriterien auswählt. Welche Überschrift verspricht die größte Unterhaltung? Welches Aufhängerbild macht euch neugierig auf den Text? Welche Zeitschrift habt ihr schon lange nicht mehr gelesen? Kaum habt ihr euch für eine kleine Auswahl entschieden, legt euch der Diener die nächsten Ausgaben auf den Schreibtisch.

Eines nahen Tages fragt ihr euch, womit eine eurer Lieblingszeitungen gerade titelt, und kommt unterwegs an einem Kiosk vorbei. In euch reift die Erkenntnis: Warum zur Hölle beauftrage ich einen Diener, mir stapelweise Artikel zu beschaffen, die ich größtenteils nicht lese, wenn ich mir die Artikel, die ich lesen möchte, selber besorgen kann?

Der Informationsflut entkommt man, indem man eine überschaubare Vorauswahl an Quellen trifft. Doch dazu genügt mir auch eine Handvoll Browser-Lesezeichen oder die allmächtige Omnibox-Adressleiste.

Tatsächlich abonniere ich seit Jahren keine RSS-Feeds mehr. Das hat zur Folge, dass ich nicht über neue Blog-Einträge von mir sympathischen Autoren informiert werde. Zu dem Umstand, dass ich sowieso eine Schlampe bin, was Kommentare auf anderen Blogs angeht, gesellt sich darum auch eine nicht unbeträchtliche Verspätung. Gelegentlich kriege ich aber auch über Twitter mit, wenn jemand etwas Interessantes schreibt – und ich nutze Twitter selber (auch) für kurze “Programmhinweise”.

Benutzt ihr noch RSS-Reader, abonniert ihr noch Blogs?

Posted in Allgemein | 5 Comments

Anime-Review: Afro Samurai – Resurrection

Worum es geht

Nachdem seine Rache an Justice, dem Mörder seines Vaters, gestillt ist, zieht sich Afro in ein Einsiedlerleben zurück. Doch plötzlich findet er sich selber in der Rolle des verhassten Schlächters wieder, als die Schwester seines ehemaligen Freundes und Kampfgefährten Jinno alias Kuma ihm das Stirnband Nummer eins abnimmt, die Überreste seines Vaters schändet und diesen mit Hilfe eines anderen alten Bekannten von Afro wiederbeleben will, um ihn langsam und qualvoll wieder umzubringen … Sios ganz eigene Rache an Afro für das Schicksal ihres Bruders.

1 Film, GONZO

Afro Samurai - Resurrection | Screenshots

Story: 8

Nein, der Anime ist sicherlich keine intellektuelle Herausforderung, sondern wie die mehrteilige Serie, auf deren Ereignissen er aufbaut, eine ziemlich blutige Abfolge von Schwertkämpfen gegen Attentäter, Samurai und Cyborgs, die sich ganz im Stil eines Quentin-Tarantino-Films um nichts anderes als spektakulär in Szene gesetzte Rache dreht. Kein Wunder, Samuel L. Jackson ist einer der Produzenten. Man muss dem Anime allerdings zugutehalten, dass er nicht mehr verspricht, als er einlöst, und er tut dies meiner Meinung nach strukturierter und weniger monoton als der Vorgänger. Ein sehr interessanter und für das Genre fast schon übermäßig selbstreflektiver Nebenstrang veranschaulicht die Gewaltspirale, die sich in der dystopischen Hightech-Samurai-Welt des Animes verlässlich weiterdreht: Afro rettet den jungen Sohn eines Mannes, den er zu seinen Zeiten als Jäger des Stirnbands Nummer 2 selber umgebracht hat. Dessen Freund und Pflegevater des Jungen schwor daraufhin, Rache an Afro zu nehmen. Ich verrate wohl nicht zu viel, wenn ich schreibe, dass Afro diesen Kampf überlebt – mit der Folge, dass nun der gerettete Junge Afros Blut sehen will … Jede Tat zieht Konsequenzen nach sich, ein Ausbruch aus dem Teufelskreislauf scheint unmöglich zu sein. Der rote Faden der Geschichte entpuppt sich bei genauerem Hinsehen als eine ewige Blutspur.

Ein bisschen unnötig, doch wohl dazu gedacht, zu unterstreichen, wie roh es in der Welt zugeht, und weil es halt den Coolness-Faktor erhöht, sind die Sex-Szenen zwischen und während (!) der Kämpfe. Auch bei der allerletzten Szene konnte ich mir ein Augenrollen nicht verkneifen, weil sie frühere Geschehnisse der Serie konterkariert. Darum mag die vergleichsweise hohe Wertung für einen Splatter-Anime zunächst verwundern, fußt aber auf einer spannend erzählten, gut strukturierten und mit einer überraschenden Metaebene ausgestatteten Geschichte.

Art & Animation: 10

Ein außerordentlich schöner Film, dessen Wirkungskraft besonders in den starken Kontrasten, einer prächtigen Farbenvielfalt und surrealistischen Schauplätzen liegt. Die Kämpfe werden mal wieder mit sehr coolen Effekten begleitet, Zeitlupe, Verzerrungen, Blitze, alles aus der Trickkiste ist vertreten. Natürlich ist Rot einer der dominierenden Farbtöne, nicht nur wegen der vielen Blutströme, sondern auch mit einer metaphorischen Bedeutung an den finalen Kampfplätzen, wodurch die folgenden Grausamkeiten bereits angekündigt werden. Man kann zu ihnen stehen, wie man will, doch sie sind klasse umgesetzt.

Musik & Sound: 10

Für Rap- und Hip-Hop-Freunde wie mich ist der Anime in musikalischer Hinsicht natürlich ein Fest, da genau wie beim Vorgänger RZA vom Wu-Tang Clan für den Soundtrack verantwortlich zeichnet. Er ist ständig präsent und fängt stets die richtige Atmosphäre ein und verstärkt sie mit starken Beats und Raps. Es ist auch der großartigen musikalischen Untermalung zu verdanken, dass die Ortschaften und ihre Bewohner einen so gesetzlosen Eindruck machen. Die Musik begleitet hier also nicht nur, sondern trägt zum Street-Life-Gefühl des Animes bei, der auch in anderen Bereichen erzeugt wird. Witzig finde ich auch die Festival-Szenen in der Mitte des Films, in denen die Musik von den Charakteren aufgegriffen wird und symbolische Bedeutung erhält, als der DJ die “Destroy All Afrosamurais”-Platte auflegt. Die deutschen und englischen Stimmen passen beide gut zu ihren Charakteren, sonderlich viel gesprochen wird sowieso nicht (mit Ausnahme von Ninja Ninja natürlich und auch von Sio).

Charaktere: 6

Die gleichen Schwächen wie schon bei der vorherigen Serie wiederholen sich im Film. Fast ausnahmslos alle Charaktere werden von einem einzigen Motiv angetrieben, nämlich Rache, und damit ist bereits alles Nötige gesagt. Gelegentlich spürt man die eine oder andere Gefühlsregung, aber die gehen in … ihr wisst schon, worin … unter. Im Gegensatz zur Serie wird dieser Umstand leider nicht durch einen irre boshaften Gegenspieler teilweise kompensiert. Diesmal müssen sich diese Rolle mehrere Charaktere teilen, die Afro außerdem mehrmals problemlos töten könnten, aber es natürlich nicht tun, weil … er leiden soll! Die übliche Erklärung von der Stange für das Versagen der Schurken, gegen den verhassten Protagonisten zu gewinnen. Kumas Schwester Sio ist skrupellos und böse in dem Glauben, angesichts ihres schrecklichen Familienschicksals ein Recht auf Skrupellosigkeit und Boshaftigkeit zu haben, ja, aber wie ihr Verhalten gegenüber dem wahnsinnigen Professor zeigt, ist sie sehr willkürlich böse, und das macht sie wiederum sehr gewöhnlich und unaufregend. Daran ändert auch das für Action-Filme übliche Sexy-Outfit nichts. Ihre Verbündeten sind leider nur inkompetente Handlanger. Immerhin konnte ich keine groben logischen Widersprüche in ihrem Verhalten bemerken. Die Charaktere sind überwiegend eindimensional, aber wenigstens nicht hirnlos wie in manch anderen Anime.

Fazit:

Packende, wenn auch teilweise übertriebene Action in einer Mischung aus dunkler Zukunftsvision und historischen japanischen Kulissen. Definitiv nichts für Zartbeseitete, doch allen anderen bietet Afro Samurai – Resurrection fantastisch inszenierte und mit stimmungsvollem Rap unterlegte eineinhalb Stunden eines diesmal umgekehrten Rachefeldzugs.

Posted in Anime | Tagged , , , , , , | Leave a comment

Welcome to the Inquisition, Jiro!

… …

Oh, hi!

Verzeihung, ihr erwischt mich gerade dabei, dieses Eisen zu polieren. Soo … hier ist noch eine matte Stelle … Wisst ihr, es hat schon lange keine Blogger mehr gebrandmarkt.

Doch kaum verstummen die Schreie der Gepeinigten, treten die Sünder den guten Geschmack wieder mit Füßen! Dies ist Kelhim’s Inquisition!

Kelhim's Inquisition

Jiro von NeoNeko.net will die hundert beliebtesten Mainstream-Anime herausgefunden haben.

Ha, dass ich nicht lache!

Diese Top 100 ist ganz offensichtlich ein Fake, um uns ganz subtil zu teilnahmslosen Idioten zu erziehen. Bestimmt ein geheimes Propagandaprogramm der Regierung. Warum sonst würde es wohl die hirnlose Tittenshow Seikon No Qwaser unter die beliebtesten hundert Anime schaffen? Wann wäre der Mainstream jemals so niveaulos gewesen?

Mein Vorwurf ist völlig aus der Luft gegriffen? Oh, warum nennt er wohl seine Quelle nicht, hm? Aber ich durchschaue sein mieses Spiel! Denn es hat einen kleinen Schönheitsfehler: Er behauptet, eine Top 100 zu liefern, aber in Wirklichkeit listet er nur 98 Anime auf. Hätte er mal lieber auf die Nummerierung verzichtet, dann wäre sein diabolischer Plan vermutlich auf ewig unentdeckt geblieben. Natürlich fällt so ein numeraler Fehler selbst den Dümms…

Oh mein Gott!!

Neun von zehn Kommentatoren fiel gar nicht auf, dass die Liste mit 98 endet. Es ist alles viel schlimmer, als ich befürchtet hatte. Jiro will nicht nur den Mainstream manipulieren, damit er seine kostbare Lebenszeit in endlosen Filler-Episoden aller Dragon-Ball-Staffeln verschwendet, nein, seine Strategie zeigt sogar schon erste Erfolge!

Freunde, wir dürfen nicht zulassen, dass Jiros düstere Zukunftsvision irgendwann Realität wird!

Wollen wir unseren Kindern und Enkelkindern wirklich eine Welt überlassen, in der K-ON!! siebzig Plätze vor Black Lagoon: The Second Barrage liegt? Können wir es mit unserem Gewissen vereinbaren, dass D.Gray-man einen Vorsprung von siebzig Plätzen gegenüber dem formidablen Ergo Proxy hat? Was zum Teufel ist überhaupt Kaichou wa Maid-sama!? Ich habe nie davon gehört, und selbstverständlich ist alles schlecht, was ich nicht kenne.

Hiermit verurteile ich Jiro zu hundert Jahren Einzelhaft bei Wasser, Brot und einer Endlosschleife von Vampire Knight. Sollte er als Kronzeuge seine Quelle verraten, bin ich jedoch gewillt, die Strafe auf 98 Jahre zu reduzieren.

Und wieder einmal wurde der Gerechtigkeit und dem gesunden Menschenverstand Genüge getan!

Posted in Kelhim's Inquisition | Tagged , , | 4 Comments

Mirror, Mirror on the Map … Spiegelbilder fürs 2D-Tileset

Auch wenn ich seit einem Jahr nicht mehr über meinen RPG-Editor geschrieben habe, hat die Entwicklung vor allem im vergangenen Herbst einen entscheidenden Schub bekommen. Seitdem habe ich den Code außerdem nach Python 3 portiert, ihn dokumentiert und ihn überwiegend an den De-facto-Styleguide PEP 8 angepasst. Von nun an plane ich den Aufbau der Spielengine, wenn ich Zeit und Lust habe.

Heute beschäftigt mich die Frage, ob und wie ich Spiegelbilder ermögliche, z.B. von Figuren im Wasser. Das ist bei 2D-Karten nicht so einfach, wie man denkt. Die einzelnen Felder auf der Karte können Terrainübergänge zeigen, also Stein über Gras über Sand über Wasser. Diese Terrainübergänge können allerdings auch animiert sein, also Stein über animiertem Gras über Sand über animiertem Wasser – oder beliebige andere Kombinationen. Damit diese Übergänge im Spiel nicht immer wieder neu gemalt werden müssen, werden sie nur einmal gemalt und dann als jeweils ein Bild zwischengespeichert. Stellt sie euch wie zu einem gemeinsamen Bild vereinigte Ebenen in einem Bildverarbeitungsprogramm vor.

Quizfrage: Wie schiebt man jetzt nachträglich ein Spiegelbild in diese Terrain-Sandwiches?

Tileset-Terrainübergänge

A) Machen wir es genauso wie mit den Terrains! Jedes Spiegelbild wird einmal mit dem darunterliegenden spiegelnden Feld kombiniert und zwischengespeichert! Oh, natürlich müssen wir das bei animierten Feldern für jedes Einzelbild tun! Oh, natürlich müssen wir den Terrainübergang neu malen, um das Spiegelbild zwischen die spiegelnden und nicht-spiegelnden Oberflächen zu klemmen! Oh, und natürlich müssen wir das auch für jedes Einzelbild des Spiegelbildes tun, wenn es selber animiert ist, z.B. ein tanzender NPC. Oh, und natürlich müssen wir es für jede 32 x 32 Pixel große Teilgrafik jedes Spiegelbild-Einzelbildes tun, denn die Spiegelbilder können größer als 32 x 32 Pixel sein und sich in beliebig vielen Nachbarfeldern spiegeln! Ich hoffe, ihr habt Kombinatorik im Mathematikunterricht gelernt, denn jetzt wäre ein ziemlich guter Zeitpunkt, eine Formel dafür aufzustellen, was für ein Wahnsinn diese Methode ist.

B) Okay, das ist viel Rechenarbeit im laufenden Spiel – aber könnten wir diese ganzen Einzelbilder nicht ein einziges Mal in einem großen Rutsch erstellen, in einer handlichen Binärdatei speichern und dann im Spiel bequem nachladen, wenn wir sie brauchen? Oh, natürlich wäre das angesichts der unendlichen Kombinationsmöglichkeiten eine riesige Datei, die um ein Vielfaches größer wird als alle Bilddateien des Spiels zusammengenommen, den Download des Spiels wahnsinnig aufbläht und beim Spielstart den Arbeitsspeicher mit vielleicht oder vielleicht auch nicht genutzten Bilddateien zumüllt! Oh, natürlich könnten wir diese Datei auch pro Karte erstellen und damit die Dateigrößen auf der Festplatte und den Speicherverbrauch ganz erheblich reduzieren, aber damit gleichzeitig die Kombinationen, die auf mehr als einer Karte möglich sind, doppelt, dreifach, vierfach, n-fach speichern und die Gesamtgröße des Spiels damit noch einmal um ein Vielfaches aufblähen! Okay, es bleibt wohl eine schlechte Idee mit hohen Kosten für ein bisschen eye candy.

C) Geben wir jeder Figur auf der Karte ein eigenes Spiegelbild-Item, das ständig existiert und bei jeder Veränderung der Figur-Grafik entsprechend angepasst wird! Aber dieses spezielle Item lebt unterhalb der Karte und ist daher niemals sichtbar, es sei denn … die spiegelnden Teilbereiche eines Feldes sind semi-transparent! Die Tilesets müssen also entsprechend angepasst sein. Doch halt! Bei Wasser ist das zwar kein Problem, weil Wasser normalerweise keine anderen Terrains überlagert, sondern selber überlagert wird. Wenn aber eine spiegelnde (= semi-transparente) Glasoberfläche über einer Steinfläche liegt, bleibt der Bereich ja undurchsichtig und spiegelt daher nicht! Das ist wahr, aber vernachlässigbar, da es nur Randbereiche eines Feldes betrifft – vielleicht lässt es sich aber auch mit einem Paint-Verfahren lösen, das transparente Pixel eines Terrains vernichtend (wie einen Radiergummi) begreift: Dann würden die halb-durchsichtigen Pixel des Glas-Bildes die undurchsichtigen Pixel des Stein-Bildes ersetzen, statt mit ihnen addiert zu werden. Nachfolgende undurchsichtige Bilder könnten dann aber wieder wie gewohnt das Glas überschreiben.

Tileset-Spiegelbilder

Überraschenderweise werde ich wahrscheinlich Methode C ausprobieren, wenn ich mit der Entwicklung so weit bin. Es ist gut, solche scheinbar nebensächlichen Details vorher zu klären, damit man kein Fundament baut, das es schwierig bis unmöglich macht, sie später noch hinzuzufügen.

Posted in Programmieren | Tagged , , | Leave a comment

Life of Py: Simples Kampfsystem in Python

Heyho, aufstrebender Pythonista! Du hast jetzt genug Einkaufslisten erstellt, dutzendmal den ollen Zinseszins berechnet und bringst dich beim nächsten “Hello world”-Programm um? Du willst endlich deinem Traum näher kommen und ein eigenes Spiel in Python schreiben? Perfekt! Lassen wir also diesen Unsinn und legen wir los.

Simple battle system for python (console).

Wir beginnen mit einem simplen, RPG-ähnlichen Kampfsystem, das wir in den folgenden Artikeln ausbauen werden. Natürlich kannst du den kompletten Code einfach kopieren und damit anstellen, was du willst, aber es wäre vielleicht cool, wenn du wüsstet, was du da kopierst. ;)

Kein Kampf ohne Kämpfer, hier ist der Bauplan:

Wir können mithilfe des Konstruktors bestimmen, wie der Kämpfer heißt, auf welchem Level er sich befindet und ob entweder der Spieler oder der Computer ihn steuert. Dann setzen wir nicht nicht einfach feste Werte für Hitpoints, Angriffsstärke, Verteidigung und Flinkheit, sondern lassen sie von einer globalen attribute()-Funktion in Abhängigkeit vom Level und des Zufalls bestimmen. Die Funktion sieht so aus:

Um zu verstehen, was diese Funktion macht, stell dir eine exponentiell steigende Kurve in einem Koordinatensystem wie in der Schule vor und greif dir einen beliebigen Punkt aus der Kurve heraus. Je weiter rechts der Punkt liegt, desto höher ist der Level, und weil die Kurve steigt, spuckt die Funktion auch einen höheren Attributwert aus als bei den weiter links liegenden Punkten. Die Formel für die Kurve ist ziemlich simpel: Der Level dient als Basis, und der Exponent von 1 wird um einen zufälligen Wert zwischen mindestens 0 und höchstens 0.1 erhöht, je nach dem, ob eine Abweichung in beide Richtungen(deviation) angegeben ist. Das Zufallselement ist wichtig, damit die Kämpfer selbst bei gleichem Level noch individuelle Stärken und Schwächen haben. Das Ergebnis wird dann noch mit einem beliebigen Faktor multipliziert.

Ein Kämpfer muss ordentlich zuschlagen können, darum fügen wir der Fighter()-Klasse eine hit()-Methode zu:

Die kriegt den Gegner als Argument mitgeteilt und berechnet nach einer einfachen Formel den Schaden, den sein Schlag verursachen würde. Genau wie in richtigen Fights fällt nicht jeder Schlag immer genau gleich aus, darum schwankt hier der tatsächliche Angriff zwischen 30% und 170% des eigenen Angriffswertes. Davon ziehen wir zwischen 0% und 50% des aktuellen gegnerischen Verteidigungswertes ab. Dann prüfen wir mit der beats()-Methode, ob der Angreifer in diesem Moment flinker als sein Gegner reagiert, und falls ja, berührt der Schlag den Gegner – falls nicht, konnte der Gegner ausweichen. Die beats()-Methode ist ziemlich clever, weil sie Zufall simuliert und darum auch bei gleichen Attributen verschiedene Ergebnisse liefern kann:

Mithilfe der globalen compare()-Funktion berechnet sie das Verhältnis des eigenen und gegnerischen Attributes in Prozent und dann die Differenz von gegnerischem und eigenem Prozentsatz – das Ergebnis ist entweder negativ oder positiv, aber nie weniger als -100 und nie mehr als 100. Eine Formel, die im Grunde genauso funktioniert wie in attribute(), berechnet schließlich die Chance des Gegners, den Kämpfer in einem Attributvergleich zu schlagen. Je größer eine positive Differenz ist, desto höher ist die Potenz und desto kleiner ist der Teiler, und desto größer ist im Ergebnis die Chance für Kämpfer 2, Kämpfer 1 im Vergleich zu schlagen. Für eine negative Differenz gilt jeweils das Gegenteil.

Die compare()-Funktion ist dagegen extrem simpel:

Sie gibt einfach nur die Prozentsätze der beiden Kämpfer für ein Attribut zurück, wobei der stärkere mit 100% den Orientierungswert bietet. Diese Funktion wird auch später von der “AI” benutzt, um Entscheidungen zu treffen.

Gut, Kämpfer können nun Schläge austeilen. Aber was passiert, wenn jemand getroffen wird? Das erledigt die get_hit()-Methode der Fighter()-Klasse:

Die hit()-Methode des Angreifers teilt dem Verteidiger den berechneten Schaden mit. Wenn der kleiner oder gleich Null ist, kann der Verteidiger diesen Schlag abwehren, ansonsten wird ihm der Schaden von den Hitpoints abgezogen. Wenn ihm dadurch die Hitpoints ausgehen, markieren wir den Kämpfer als besiegt. Das Spiel reagiert später darauf. Die get_defeated()-Methode ist ausgelagert, weil sie in Zukunft noch mehr erledigen könnte. Im Moment ist sie ganz spartanisch:

Unter Umständen zieht es ein Kämpfer vor, in einer Runde auf einen Angriff zu verzichten und stattdessen seine Chancen zu verbessern, den nächsten Schlag des Gegners abzuwehren. Dafür beherrscht er die defend()-Methode, die einen zufälligen Faktor zwischen 1.1 und 1.3 bestimmt, mit dem sein Verteidigungswert beim nächsten gegnerischen Angriff multipliziert wird.

Oder er könnte versuchen, dem Kampf zu entfliehen, hier die escape()-Methode:

Hier benutzen wir wieder die beats()-Methode, diesmal um den Fluchtversuch zu simulieren, und wieder ist die Flinkheit das entscheidende Attribut. Der Rückgabewert steht für den Erfolg oder Misserfolg der Flucht und wird in der Spielschleife aufgegriffen, die darauf angemessen reagiert.

Wo wir von der Spielschleife sprechen …! Jetzt haben wir unsere Kämpfer, doch wie lassen wir sie gegeneinander antreten? Weil das Programm noch nicht besonders komplex ist, können wir die Logik komfortabel in der main()-Funktion des Programms ausbreiten:

Erst einmal erstellen wir natürlich zwei Kämpfer, und blue_fighter soll vom Spieler gesteuert werden. Dann bedienen wir uns wieder einmal der beats()-Methode, um zu erfahren, wer das Flinkheits-Duell gewinnt und als Erster angreifen darf. Die while-Schleife ruft nun so lange die globale fight()-Funktion auf, bis der Kampf auf irgendeine Art beendet wird (Sieg bzw. Niederlage, Flucht, Spielende). Die turn_update()-Methode ist dafür gedacht, etwaige temporäre Zustände wieder zu entfernen und Standardwerte wiederherzustellen, aber bisher setzt sie nur den temporären Verteidigungsbonus von defend() wieder zurück:

Die globale print_stats()-Funktion zeigt dem Spieler vor jedem Zug eines Kämpfers die Hitpoints der Kämpfer an, damit er eine begründete Entscheidung treffen kann:

Kommen wir nun zur fight()-Funktion, in der die wesentlichen Entscheidungen der Kämpfer getroffen und bearbeitet werden:

Hier teilt sich der Weg: in einen für vom Spieler kontrollierte und einen für vom Computer gesteuerte Kämpfer. Erstere erhalten mit der fight_options()-Funktion ein Menü angeboten, in dem sie ihren Zug bestimmen, und je nach Auswahl wird die entsprechende Fighter()-Methode ausgeführt. Wenn daraufhin der Kampf weiterläuft, gibt die Funktion True zurück, doch falls der Kampf durch irgendeine Auswahl oder einen besiegten Kämpfer beendet wird, gibt sie False zurück, worauf main() mit dem Abbruch der Spielschleife, break, reagiert (siehe oben).

Sehen wir uns kurz die Kampfoptionen von fight_options() an:

Der Spieler trifft seine Entscheidung also über ein Eingabefeld in der Konsole, und der entsprechende String wird fight() mitgeteilt.

Wesentlich interessanter ist die decide()-Methode der Fighter()-Klasse, die von fight() aufgerufen wird (siehe oben). Sie ist unsere “AI”, unsere künstliche Intelligenz für die vom Computer gesteuerten Kämpfer, obwohl sie in diesem Fall sehr einfach gestrickt ist:

Zunächst holt sich die “AI” die Prozentsätze seines eigenen Kämpfers für Angriffsstärke, Verteidigung und verbliebene Hitpoints im Vergleich zu denen seines Gegners. Wenn der computergesteuerte Kämpfer schwächer abschneidet, wird ein Prozentsatz kleiner als 100 sein. Das ist noch kein Grund für Gefahr. Aber falls sowohl seine eigene Angriffsstärke als auch seine Verteidigung weniger als 20% der gegnerischen Werte ausmachen oder falls sie weniger als 50% ausmachen und dafür die Hitpoints weniger als 20% der gegnerischen Hitpoints entsprechen, sieht der Computer einen gewichtigen Grund, die Flucht anzutreten, statt sich auf einen aussichtslosen Kampf einzulassen. Wenn lediglich die Angriffsstärke nur 20% des gegnerischen Wertes ausmacht, probiert er es mit Verteidigung, um einen Schlag abzuwehren. In allen anderen Fällen greift der Computer seinen Gegner an. Die decide()-Methode gibt außerdem den Wert True zurück, falls der Kampf weitergeht, und False, falls der Kampf durch Sieg bzw. Niederlage oder eine erfolgreiche Flucht beendet wird.

Weil wir ein paar Mal die in der Python-Standardbibliothek enthaltene round()-Methode benutzen, um Dezimalzahlen zu runden, diese Methode aber seit Python 3 nicht mehr dem Verfahren entspricht, wie man es aus der Schule kennt und erwartet (“Runde immer ab .5 auf!”), sondern bei geraden Vorkommastellen vor .5 ab- und bei geraden aufrundet, stellen wir das alte und erwartete Verhalten wieder her, indem wir in unserem Programm round() mit einer globalen Funktion überschreiben:

Damit ist die Konsolen-Version unserer RPG-artigen, rundenbasierten Kampfengine so gut wie fertig. Fehlen nur noch die Importe und ein paar Konventionen:

Die komplette Fassung als Python-Skript: simpleFight

Nachdem wir jetzt verstanden haben, wie die Kämpfer interagieren und die Züge sich abwechseln, reisen wir in der nächsten Ausgabe in die 90er Jahre und spielen mit grafischer Oberfläche, Maus und diesen ganzen neumodischen Interfaces, die sich niemals durchsetzen werden! Und weil ich keine sadomasochistische Ader habe, setze ich auf Qt und PyQt.

Posted in Programmieren | Tagged , | Leave a comment