Tutorial: Wie entsteht ein Strategiespiel

Gute Links und Tutorials könnt ihr hier posten.
Antworten
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich wollte schon immer mal ein Tutorial zum Thema Spiele-Entwicklung schreiben. Wäre das interessant für euch?

Allerdings geht es mir dabei nicht um die Art von Spiel, an die die meisten sofort denken würden, sondern ich habe mir den fast 40 Jahre alten Klassiker WARPWAR von Howard Thompson herausgesucht und würde davon einzig die Regeln umsetzen, sodass man daraus z.B. ein P(E)BM/Browser-Spiel machen könnte. Grafik wird es keine haben.

Mir geht es um den Prozess, wie ich aus einer Beschreibung zum einem Python-Programm komme. Mir geht es nicht darum, wie pygame, openGL oder sonst eine Bibliothek funktioniert.

Bei WARPWAR versuchen zwei Spieler (oder Spielerinnen) mit ihren Raumschiffen auf einer Sternenkarte jeweils die Heimatwelt des anderen zu erobern. Raumschiffe können individuell konstruiert werden und das Spiel kommt nach dem Stein-Schere-Papier-Prinzip ohne Würfel aus.

Das Spiel findet in Runden statt. Jede Runde hat eine Bauphase, eine Bewegungsphase und bis zu drei Kampfphasen. Eigentlich findet die Bewegung nacheinander statt, doch ich straffe das mal zugunsten einer einfacheren Ausdehnung der Regeln auf mehr als zwei Spieler. In der Bauphase kann man neue Raumschiffe erschaffen und muss ein Maß zwischen Bewegung, Bewaffnung, Verteidigung und Kosten finden. In der Bewegungsphase werden alle Schiffe bewegt. Es gibt zwei Arten: Warp-Schiffe, die zwischen Welten verkehren und Systemschiffe, die von Warp-Schiffen transportiert werden müssen, wenn man sie bewegen will. Treffen Schiffe zweier Spieler aufeinander, kommt es zu einem Kampf. Hier gilt es die richtige Taktik zu finden und die Schiffsenergie zwischen den verschiedenen System so zu verteilen, dass man einen Vorteil gegenüber dem Gegner hat, der das selbe versucht.

Basierend auf dem aktuellen Spielstand gibt jeder Spieler für jede Phase seine Befehle unabhängig von allen anderen Spielern ab. Diese werden dann von dem Programm eingelesen, verarbeitet und ein neuer Spielstand wird erstellt und an alle Spieler verteilt. Das Programm prüft dabei, ob die Befehle regelkonform sind und erstellt Reports, die dem Spieler helfen, zu verstehen, was passiert ist.

Das Spiel sieht daher ungefähr so aus:

Code: Alles auswählen

    def process_turn():
        game = Game.load()
        game.prepare_phase()
        for player in game.players:
            player.orders = Order.load(player.name)
        game.process_orders()
        for player in game.players:
            player.generate_report()
        game.save()
An den Rest würde ich mich machen, wenn es ein paar Stimmen gibt, die das ganze interessant finden :)

Stefan
LivingOn
User
Beiträge: 33
Registriert: Montag 11. August 2008, 07:53

das finde ich eine super Idee!

Wenn Du es Dir zutraust und schrittweise in das Thema einführst, so dass auch Einsteiger damit klar kommen, dann wird es die Welt sicherlich bereichern ;-)
Lasse
User
Beiträge: 112
Registriert: Donnerstag 3. Februar 2011, 18:25

Das ist auf jeden Fall eine super Idee!
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Bin auch gespannt. Ein *Thumbs-Up* dafuer schon im Voraus.
:wink:
yipyip
Zuletzt geändert von yipyip am Montag 8. August 2011, 14:47, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Bin auch für eine Fortsetzung!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

OK, dann geht es in Kürze weiter. Vielen Dank für das Feedback.

Stefan
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Beginnen wir damit, eine geeignete Repräsentation des Spiels und seiner Regeln als Programm zu finden. Da ich dieses Tutorial schreibe, ohne das ganze bis zum Ende durchgeplant zu haben, finde ich jetzt vielleicht nicht die optimale Repräsentation, aber irgendwie müssen wir ja anfangen.

Vielleicht hat sich der eine oder die andere bereits auf die Suche nach den Regeln begeben. Man findet sie mit Google. Es sind etwa 20 Seiten, die ich hier nicht alle wiedergeben will (oder kann, denn auch nach 40 Jahren unterliegen sie natürlich noch einem Copyright).

Hier ist eine Kurzfassung:

Gespielt wird auf einer Karte mit 15 x 22 durchnummerierten Hex-Feldern. 28 Hex-Felder enthalten Sternsysteme, sechs davon sind Heimatwelten. Zwischen einigen Sternen gibt es Warp-Linien.

Es spielen zwei Spieler gegeneinander. Jeder Spieler besitzt Raumschiffe. Er verfügt über Baupunkte, mit denen er neue Schiffe erschaffen oder vorhandene repariert kann. Wer zuerst drei Siegpunkte gesammelt hat, gewinnt. Einen Siegpunkt bekommt, wer eine gegnerische Heimatwelt kontrolliert.

Raumschiffe sind entweder Warp-Schiffe oder Systemschiffe. Sie befinden sich immer auf einem Hex-Feld, welches beliebig viele Schiffe (beliebig vieler Spieler) enthalten kann. Ein Warp-Schiff kann sich über die Karte bewegen und dabei Systemschiffe transportieren. Systemschiffe können sich ohne transportierendes Warp-Schiff nur auf einem Feld mit einem Sternsystem aufhalten.

Raumschiffe haben diverse Eigenschaften: Power/Drive (PD), Warp Generator (WG), Beams (B), Screens (S), Tubes (T), Missiles (M), Systemship Racks (SR) und Level.

Im Kampf bekommt jedes Raumschiff eine Taktik zugewiesen und kann beschädigt (oder zerstört) werden. Details betrachte ich erst, wenn es um den Kampf geht.

Das Spiel findet in Runden und Phasen statt. Jeder Spieler gibt pro Phase Befehle ab. Dazu liegt ihm eine Beschreibung des Spielstands vor. Er erhält einen Report mit der aktualisierten Beschreibung des Spielstands.


Mein Datenmodell soll aus Exemplaren der folgenden Klassen bestehen.

Ich werde im folgenden Python 3.2 benutzen. Python bietet leider keine gute Möglichkeit, ein Datenmodell zu beschreiben, daher gebe ich jeweils eine `__init__`-Methode an, die alle Eigenschaften eines Objekts initialisiert.

Code: Alles auswählen

    class Game:
        def __init__(self):
            self.players = []       # Liste beide Spieler
            self.stars = []         # Liste der 27 Sternsysteme
            self.eships = []        # Liste alle Raumschiffe aller Spieler
            self.turn = 0           # Rundenzähler
            self.phase = 0          # Phasenzähler
Ein Objekt der Klasse `Game` soll den Ist-Zustand des Spiels beschreiben. Es kennt Spieler (`Player`), Sternsysteme (`Star`) und Raumschiffe (`Ship`). Ich denke, ich komme ohne eine explizite Karte aus. Stattdessen haben `Star`s und `Ship`s eine Position. Über das `Game`-Objekt kann ich dann alle Raumschiffe an einer bestimmten Position finden.

Code: Alles auswählen

    class Player:
        def __init__(self):
            self.name = ""          # Wie nennt sich der Spieler
            self.password = ""      # Kennwort, damit nicht jeder Befehle einreichen kann
            self.bp = 20            # Baupunkte
            self.vp = 0             # Siegpunkte
            self.orders = []        # Liste aller Befehle für die aktuelle Phase
    
    class Order:
        def execute(self, game, player):
            pass
Ein `Player` hat eine Liste von Befehlen (`Order`) und später noch einen Report (`Report`), den ich mir noch nicht weiter überlegt habe. Befehle haben Parameter wie z.B. das Raumschiff, das bewegt werden soll und das Zielfeld, wohin es bewegt werden soll, aber ich denke, `Game` und `Player`, die jeder Befehl kennen muss, damit er ausgeführt werden kann, übergebe ich und speichere ich nicht als Teil eines jeden Befehls.

Code: Alles auswählen

    class Star:
        def __init__(self):
            self.name = ""          # Name des Sternsystems
            self.position = Coord() # X/Y-Koordinate
            self.owner = None       # Ist es eine Heimatwelt? Dann hier Spieler
            self.warp_lines = []    # Liste der durch Warp-Linien verbundenen Systeme
Ein `Star` hat einen `Player` als Besitzer, wenn er eine Heimatwelt ist. Er hat eine Reihe von Warp-Linien-Verbindungen zu anderen Sternsystemen. Die Position repräsentiere ich als Objekt, das weiß, welche Koordinaten die sechs Nachbarfelder haben.

Code: Alles auswählen

    class Ship:
        def __init__(self):
            self.no = 0             # Identifikator
            self.position = Coord() # X/Y-Koordinate
            self.owner = None       # Besitzer (ein Spieler)
            self.level = 0          # Tech-Level
            self.pd = 0             # Power/Drive
            self.wg = 0             # Warp Generator (0=Systemship, 1=Warp-Ship)
            self.b = 0              # Beams
            self.s = 0              # Screens
            self.t = 0              # Tubes
            self.m = 0              # Missiles
            self.sr = 0             # Systemship Racks
            self.ships = []
Ein `Ship` hat ebenfalls eine Position und immer einen `Player` als Besitzer und wenn es ein Warp-Schiff ist, kann es Systemschiffe transportieren. Die Liste der Attribute ist in sofern noch nicht vollständig, als das es jedes Attribut einmal für den maximalen Wert und für den aktuellen Wert geben muss und ich wohl auch noch für einen Kampf mir merken muss, welche Taktik benutzt werden soll und welchen Schaden ein Schiff genommen hat. Um es aber einfach zu bauen, reicht das so erst einmal.

Nach dieser Vorplanung kann ich das nächste Mal mit der Implementierung beginnen.
Zuletzt geändert von sma am Montag 15. August 2011, 11:26, insgesamt 1-mal geändert.
BlackJack

@sma: Diese ganzen ein- bis zweibuchstabigen Abkürzungen mit der Bedeutung als Kommentar dahinter finde ich unschön.
deets

Und "self.wg" scheint boolsch zu sein - warum dann "0 oder 1"? Ausserdem sind das zumindest in Py2.x old-style-Klassen. Kann ja sein, dass du von Python 3 ausgehst, habe ich aber nirgends gesehen.
Bolitho
User
Beiträge: 219
Registriert: Donnerstag 21. Juli 2011, 07:01
Wohnort: Stade / Hamburg
Kontaktdaten:

Finde Dein Vorhaben super! Mach weiter so!

Habe ein fertiges eigenes Spielkonzept aus Jugendtagen vor mir liegen. Das könnte man ganz ähnlich umsetzen. Vielleicht hänge ich mich einfach dran und mach mich mal an die Arbeit.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Bolitho, nur zu.

Deets, ich nutze Python 3 (habe ich jetzt im ersten Text ergänzt). Das wg explizit eine Zahl ist, wollte ich nutzen, um leichter die Kosten für ein Schiff auszurechnen. Ich finde es unschön, wenn ich ausnutze, dass True==1 ist, möchte aber einfach den Wert mit den Kosten multiplizieren können. Dafür nutze ich aus, dass alles != 0 als "wahr" gilt.

BlackJack, Namen sind verdammt schwer. Ich habe die Begriffe aus den Regeln (also meiner Spezifikation) benutzt, da es in der Regel eine gute Idee ist, die Sprache der Domäne zu benutzen. Dort wimmelt es von diesen Abkürzungen. Du hast aber recht, unschön ist es trotzdem und vielleicht ändere ich das noch mal bei den Raumschiffen.

Stefan
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@sma: Können Warp-Schiffe sich nur über Warp-Linien bewegen? Wenn ja: Wieso gibt es dann so viele "leere" Felder, die ja dann niemals betreten werden können? Oder beschleunigen Warp-Linien eine Reise nur, ähnlich wie Straßen oder Eisenbahnlinien in div. Strategiespielen wie Civilisation, Battle for Wesnoth usw.? Wenn diese leeren Hex-Felder nicht betreten werden können, könnte man ja imho komplett darauf verzichten und auf ein reines Graphenmodell setzen, bei welchem die Kantengewichte den Abstand zwischen zwei Systemen repräsentieren - insofern vermute ich ja schon, dass diese einen Sinn haben :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Trichter
User
Beiträge: 45
Registriert: Montag 20. April 2009, 10:21

Hyperion hat geschrieben:@sma: Können Warp-Schiffe sich nur über Warp-Linien bewegen? Wenn ja: Wieso gibt es dann so viele "leere" Felder, die ja dann niemals betreten werden können? Oder beschleunigen Warp-Linien eine Reise nur, ähnlich wie Straßen oder Eisenbahnlinien in div. Strategiespielen wie Civilisation, Battle for Wesnoth usw.? Wenn diese leeren Hex-Felder nicht betreten werden können, könnte man ja imho komplett darauf verzichten und auf ein reines Graphenmodell setzen, bei welchem die Kantengewichte den Abstand zwischen zwei Systemen repräsentieren - insofern vermute ich ja schon, dass diese einen Sinn haben :-)
Auszug aus den Regeln von http://www.contrib.andrew.cmu.edu/usr/g ... rpwar.html

Code: Alles auswählen

WARP GENERATOR (WG) is the unit that allows a ship to move from star to star through space and to jump along Warplines. A ship with a warp generator is a Warpship. Ships without warp generators are Systemships.
Antworten