Warning: include_once(/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php): failed to open stream: No such file or directory in /var/www/html/fields/dbp13/local/config.php on line 4

Warning: include_once(): Failed opening '/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php' for inclusion (include_path='.:/opt/php/lib/php') in /var/www/html/fields/dbp13/local/config.php on line 4

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fields/dbp13/local/config.php:4) in /var/www/html/pmwiki-2.2.86/pmwiki.php on line 1250
Datenbankpraktikum SS 2013 - MMOG - Schiffe Und Missionen Missionen

Missionen

von Christian Heiden

Einleitung

Meine Aufgabe innerhalb der Gruppe "Schiffe und Missionen" bestand darin, dafür zu sorgen, dass Flotten zu anderen Planeten fliegen können. Dafür sollte es verschiedene Auftragstypen (Missionen) geben. Ich habe den Abschnitt in Model und View eingeteilt um die jeweilige Einordnung deutlich zu machen. Auf Details des jeweiligen Controllers gehe ich in Teilen der View ein. Das folgende Bild zeigt die Struktur meines Aufgabenteils. Es gibt zwei Views, welche beide über ihre jeweiligen Controller auf das Model der Flotte zugreifen.


Struktur der Implementation (MVC)

Flotten sind in der Lage auf eine Mission geschickt zu werden. Es gibt insgesamt 5 Missionstypen:

  • Reise: Flotte wird an einem anderen Planeten stationiert
  • Kolonialisierung: Flotte ist auf dem Weg einen anderen Planeten zu besiedeln
  • Angriff: Flotte fliegt zu einem Planeten um ihn anzugreifen
  • Spionage: Flotte fliegt zu einem Planeten um ihn auszuspionieren
  • Transport: Flotte fliegt zu einem Planeten um ihm Rohstoffe zu liefern

Bei jedem Missionstyp gibt es wiederum 4 Möglichkeiten sich bei Ankunft am Zielplaneten zu verhalten, da dieser verschiedene Zustände haben kann:

  • unbekannt (kein Spieler ist dort angesidelt)
  • besiedelt von einem Freund aus der Allianz
  • eigener Planet
  • Planet eines gegenerischen Spielers

Das ergibt insgesamt 20 Fälle die zu beachten waren bei der Implementation des Bewegungsmechanismus'. Eine detaillierte Übersicht bietet folgendes Bild.


Aktionen die am jeweiligen Zielplaneten bei einer bestimmten Mission durchgeführt werden

Model

Vorüberlegungen

Die Schwierigkeit bei der Planung der Missionen fing bei der Zuordnung der Attribute der Flotten an. Wir brauchten so viele Attribute, dass sich eindeutig aussagen lässt wo sich die Flotte genau befindet, wohin sie fliegt, wann sie ankommt, etc. Gleichzeitig, wollten wir nicht zu viele Attribute einführen um eventuelle Redundanzen zu vermeiden.
Da Flotten beim Abflug Energie benötigen, mussten wir mitbedenken, dass Flotten genug Energie "tanken", sodass diese für eine Rückkehr reicht. Zusätzlich kommt hinzu, dass bei Missionstyp Travel geplant ist, seine Flotten auf anderen befreundeten Planeten stationieren zu können. Diese verteidigen den Planeten im Falle eines Kampfes zusätzlich zu der heimischen Flotte. Dort stationiert muss es also möglich sein, die Flotte wieder zurückkehren zu lassen zu ihrem Heimatplaneten und dies zu einem beliebigen Zeitpunkt.
Ferner müssen Flüge abgebrochen werden können. Dafür müssen die Attribute noch die Informationen enthalten, welche den Auftrag, der in die Resque eingefügt wurde, wieder eindeutig identifiziert um ihn dann zu löschen.
Eine weitere Schwierigkeit bestand darin Technologieerweiterungen miteinfließen zu lassen. Sollten Flotten auf einer Mission sein, muss zum Beispiel bei einem Abbruch des Fluges die Geschwindigkeit der Flotte noch mit den alten Technologiefaktoren berechnet werden.

Umsetzung

Beziehungen

Die grobe Struktur (komplettes ER-Diagramm) unseres Schemas ist die, dass jeder User mehrere Flotten haben kann, wobei er jeweils immer nur eine an einem Planeten hat, die sogenannte Heimatflotte. Eine Flotte hat eine Mission und besitzt Start-, Ziel- und Heimatplaneten. Diese mussten wir, da eine mehrfache Relation zum Model Planet besteht mit foreign_keys versehen (siehe folgenden Codeblock). Einer Flotte können mehrere Schifftypen zugeordnet sein. Da wir in der Relation zu Ship ein Attribut amount unterbringen wollten, war die Umsetzung in Rails ein wenig komplizierter. So mussten wir ein eigenes Model Shipfleet erstellen, welches in Fleet mithilfe einer has_many .. through Beziehung angesprochen wird. Weiterhin sollte beim Löschen von Flotten die Änderung auch fortgetragen werden.

 class Fleet < ActiveRecord::Base
   has_many :shipfleets, dependent: :delete_all
   has_many :ships, :through => :shipfleets
   belongs_to :start_planet, class_name: "Planet", foreign_key: "start_planet"
   belongs_to :target_planet, class_name: "Planet", foreign_key: "target_planet"
   belongs_to :origin_planet, class_name: "Planet", foreign_key: "origin_planet"
   belongs_to :user
   belongs_to :mission

   ...
 end

Attribute


Ausschnitt des ER Diagramms

Jeder User hat an jedem seiner Planeten eine Heimatflotte (eventuell auch ohne Schiffe). Sobald eine Flotte auf Mission geschickt werden soll, wird sie gesplittet und die neu entstandene Flotte erhält Attribute für den Flug (Zeit, Ziel, Mission, ...). Diese neue Flotte existiert nur solange sie unterwegs ist, denn wenn sie wieder zurückkehrt oder zu einem anderen eigenen Planeten fliegt, werden ihre Schiffe der dortigen Heimatflotte hinzugefügt und die Flotte wird gelöscht. Lediglich eine Ausnahme stellt die erfolgreiche Kolonialisierung dar, wo die Flotte am Ziel auch die neue Heimatflotte darstellt.
Diese Umsetzung war nötig um die Technologiefaktoren miteinbeziehen zu können. Sollte während eines Fluges zum Beispiel eine Technologie für erhöhte Geschwindigkeit erforscht worden sein, dann sollte sich das nur auf stationierte Flotten auswirken. Abhilfe schaffen also die temporären fliegenden Flotten, indem sie die beim Abflug aktuellen Werte für Technologien abspeichern. So kann bei Abbruch der Mission oder Rückkehr der Flotte die Zeit für den Rückweg realistisch berechnet werden.
Eine Flotte hat Attribute für Start-, Ziel- und Heimatplanet. Anhand dieser Informationen ist eindeutig ablesbar wo sie sich zu einem Zeitpunkt befindet. Durch die Angabe eines Heimatplaneten ist immer eindeutig festgelegt, ob sich die Flotte auf einem Hinflug (Startplanet = Heimatplanet), oder einem Rückflug (Zielplanet = Heimatplanet) befindet. Außerdem sind sowohl Abflugs- als auch Ankunftszeit in der Flotte abgespeichert, da dies nötig ist um Missionen abbrechen zu können. Denn mithilfe der Abflugszeit lässt sich die bisher verstrichene Zeit berechnen, welche bei die Zeitdauer darstellt, die die Flotte für ihren Heimweg benötigt.

Missionsmethoden

Um Flotten auf eine Mission zu schicken gibt es im Fleet-Model eine Methode die eine Mission, einen Zielplaneten, die Anzahl der jeweiligen Schiffe als Hash und eventuelle Rohstoffe empfängt. Der Ablauf innerhalb dieser Methode lässt sich wie folgt beschreiben:
Zunächst werden die Eingaben überprüft (Beispiel: ist das Objekt tatsächlich ein Planet). Anschließend wird die Heimatflotte gesplitted, sodass eine neue Flotte entsteht. An ihr werden dann Attribute für Start-, Ziel- und Heimatplanet, Start- und Ankunftszeit gesetzt. Daraufhin, wird geschaut, welcher Missionstyp eingegeben wurde:

 def move(mission, destination, ship_hash, ore, crystal, credit)
   case mission.id
     when 2 then # Colonization
       ... # missionsspezifische Aktionen
     when 3 then # Attack
     ...
   end
 ...
 end

In den jeweiligen cases werden missionsspezifische Aktionen durchgeführt, wie zum Beispiel dem Beladen der Flotte mit Ressourcen, was nur bei Transport und Kolonialisierung passiert. Zusätzlich wird von dort aus eine Methode aufgerufen, die dafür sorgt, dass ein Job in die Resque eingefügt wird, welcher zu dem Zeitpunkt ausgeführt werden soll, wenn die Flotte an ihrem Zielplaneten ankommt.

 Resque.enqueue_at(time, AttackPlanet, self.id, planet.id)

Bei diesem Job handelt es sich dann um die jeweilige Aktion, die eine Mission darstellt. Beispiel Angriff:

 def attack(planet)
   if other_user.nil? # unknown planet
     ... # Return
   elsif other_user == self.user # own planet
     ... # Return
   elsif other_user.alliance == self.user.alliance # alliance planet
     ... # Return
   else # enemy
     ... # Attack
   end
 end

Generell wird bei Ankunft der Flotte am Zielplaneten erst geschaut, wem der Planet gehört um dann geeignet zu reagieren. Dabei wird zusätzlich noch ein Report erstellt, welcher dem Spieler im Anschluss eine detaillierte Übersicht über den Missionsverlauf gibt. Je nach Missionstyp kehren dann die übrig gebliebenen Schiffe wieder zurück, indem erneut ein Job in die Resque gegeben wird, welcher dafür sorgt, dass die Flotte zu ihrem Ankunftszeitpunkt mit der Heimatflotte "gemerged" wird (Details zu allen möglichen Kombinationen).

Zusätzlich gibt es die Möglichkeit Missionen abzubrechen. Dazu muss der ursprüngliche Job zunächst gelöscht, die Flottenattribute neu gesetzt und ein neuer Job für die Rückkehr in die Resque eingefügt werden.

 def breakup_mission
   if self.mission.id == 2
     Resque.remove_delayed(ColonizePlanet, self.id, self.target_planet.id)
   ...
   self.start_planet = self.target_planet
   self.target_planet = self.origin_planet
   Resque.enqueue_at(self.arrival_time, ReturnToOrigin, self.id)
   ...
 end

View

Missionen


Seite: Missionen

Vorüberlegungen

Meine Idee zur Gestaltung der Missions-View war es, eine dynamische Seite zu schreiben, welche auf die verschiedenen Eingaben und den individuellen Status des Users abgestimmt sein soll. Dieses Prinzip soll, neben der erhöhten Verständlichkeit, dafür sorgen fehlerhafte Eingaben des Users zu verhindern.
Zusätzlich sollten alle speziellen Anfragen des Users ausschließlich per AJAX-Requests ablaufen, sodass ein Neuladen der Seite nicht nötig ist. Auf die AJAX-Requests soll mit einem JSON geantwortet werden der Form {"ok" : 1} oder {"ok" : 0}, sodass die nächste Aktion nur ausgeführt wird, wenn die Antwort "ok" ist. Zusätzlich werden je nach Anfragen noch weitere {Schlüssel:Wert} Paare in die Antwort gegeben.
Der generelle Ablauf soll so gestaltet werden, dass der Benutzer durch mehrere Menüs geleitet wird. Die einzelnen Menüs sollen sich nacheinander öffnen und auch nur dann, wenn im vorigen Menü eine Eingabe gemacht wurde. Dabei wird bei jeder Eingabe, ein verstecktes Input-Feld dynamisch in eine html-form eingefügt, sodass am Ende, sollte alles korrekt sein, die Form durch einen Submit-Button abgeschickt werden kann.
Zusätzlich sei zu erwähnen, dass die Seite auch mit Fehlentscheidungen umgehen kann. Sollten Änderungen vorgenommen werden an der derzeitigen Eingabe, werden entsprechende Änderungen ebenfalls intern nachvollzogen.

Umsetzung


Ablauf des Missionsmenüs

Gelangt der User auf die Seite, so ergeben sich für ihn nur die 5 Missionstypen als Buttons zur Auswahl, welche zusätzlich durch ein Popover erklärt werden, sobald sich die Maus über die Schaltfläche bewegt.

Klickt der User auf einen dieser Buttons, so wird ein weiteres Menü geöffnet, welches alle Flotten, des Users auflistet. Die Buttons der anderen Missionstypen sind nun disabled. Dort ist es möglich durch Klicken eines Pfeils die Zusammensetzung der Flotte anzeigen zu lassen (Man beachte die Bilder die beim Bewegen des Mauszeigers über die Tabellenzeilen erscheinen). Zusätzlich kann man sich die Fracht anzeigen lassen, was für Transport oder Kolonialisierung eine Rolle spielt.

Durch einen Klick auf den Haken, wird diese Flotte ausgewählt und es öffnet sich ein Menü zur Auswahl des Ziels. Hier muss zunächst die Galaxie ausgewählt werden, woraufhin die dort befindlichen Sonnensysteme in das nächste Dropdown-Menü geladen werden. Bei der Wahl des Sonnensystems, werden ebenfalls ausschließlich die Planeten in das nächste Dropdown-Menü geladen, welche in dem Sonnensystem enthalten sind. Zusätzlich steht bei befreundeten oder eigenen Planeten eine kleine Info, sodass dem User ein wenig Übersicht geboten werden kann. Dieses Verfahren verhindert ungültige Angaben, da dem User lediglich erreichbare/bekannte Galaxien/Sonnensysteme/Planeten angezeigt werden. Zusätzlich gibt es, wenn der User zu Beginn auf Spionage geklickt hat, die Möglichkeit, durch Drücken eines Buttons einen zufälligen neuen Planeten in die Liste einzufügen. So ist es dem User möglich neue Sonnensysteme/Galaxien zu erkunden und somit sein Sichtspektrum zu erweitern.

Wurde ein Planet gewählt, wird im Hintergrund ein AJAX-Request gesendet, welcher der Distanz der gewählten Flotte zum soeben ausgesuchten Planeten abfragt und anzeigt.

 GET /json/distance?planet1=<ID>&planet2=<ID>

So kann der Spieler ungefähr abschätzen, wie weit beziehungsweise lange seine Schiffe fliegen werden.
Zeitgleich zu der Anzeige der Entfernung öffnet sich eine Tabelle, wo die Anzahlen der einzelnen Schiffe eingegeben werden kann, welche die Mission ausführen sollen. Diese Tabelle enthält die Informationen über die zuvor ausgewählte Flotte. Bei Eingabe der Anzahlwerte einzelner Schiffstypen werden automatisch die summierten Werte der Flotte für Angriff, Verteidigung und Lagerkapazität angezeigt. Außerdem werden von dem Eingabefeld lediglich Zahlen angenommen, welche im Intervall zwischen 0 und der maximalen Anzahl der Schiffe stehen.

Sollte Transport oder Kolonialisierung gewählt worden sein, ist zusätzlich ein Auswahlmenü für Fracht geöffnet worden. Die Funktionialität ist ähnlich der für die Schiffsauswahl.

Um die Flotte nun abzuschicken müssen die Eingaben zunächst überprüft werden. Dazu drückt der User einen Button, wodurch ein AJAX-Request abgesetzt wird, der die Eingaben auf dem Server überprüft. Sollte es Probleme geben scrollt die Seite eigenständig nach oben und es wird eine detaillierte Fehlerangabe ausgegeben.


Fehlerangabe nach Eingabenprüfung

Per javascript wird dabei, je nach Eingabe, zum Beispiel folgender Request abgesetzt:

 GET json/check?mission=6&fleet=1&Planet=4&ship-1=5&ship-2=2&ress-ore=20000&ress-crystal=10

Auf dem Server in missions_controller.rb passiert dann folgendes: Eine Methode liest die Parameter des Requests aus und prüft die Eingaben genau so wie sie später zum eigentlichen Bewegen der Flotte verwendet werden, nur dass jeder einzelne Schritt durch einen begin/rescue Statement oder ähnlicher Fehlerbehandlung abgesichert ist. Sobald also ein bestimmter Block Fehler aufwirft, wird ein betreffender Hinweis in ein Array gepackt, welches am Schluss in einem JSON zurückgeschickt wird:

 fehler = Array.new
 fehler = check_helper(params)

 if fehler.empty?
   render :json => {"ok" => 1}.to_json
 else
   render :json => {"ok" => 0, "error" => fehler}.to_json
 end

Wenn (direkt oder nach Korrektur der Eingabe) alles in Ordnung ist (also "ok" = 1), wird ein Button freigegeben, durch den die Form abgeschickt werden kann.


erstellte Form nach Eingabe

Intern wird beim Absenden der Form erneut geprüft, ob alle Eingaben noch aktuell sind. Sollte es Probleme geben wird der User auf "Missionen", ansonsten auf "Deine Flotten" geleitet.

Sonstige Details

Da das Ziel war die Seite so zu strukturieren, dass sie fehlerhafte Eingaben verhindert, war bei der Implementation einiges zu beachten. So soll es zum Beispiel möglich sein, dass der User seine Eingaben in vorherigen Inputfeldern ändern kann und diese dennoch berücksichtigt werden. Es kam von Anfang an nicht in Frage quasi die komplette Seite in eine HTML-Form zu packen, da die Idee eine dynamische Seite zu gestalten, dies nicht ermöglicht hätte, zumindest nicht so einfach.
Die Lösung war es also an jedem Input per Javascript bzw. jQuery einen Eventhandler anzumelden, welcher bei Aktivierung die jeweilige Eingabe als ein verstecktes Input-Feld in eine Form einträgt, die am Ende abgeschickt werden kann:

 $("#travel").on("click",function () {
   ...
   $("#send").append('<input class="input-stuff" type="hidden" name="mission" value="4">');
   ...
 }

Sollte nun irgendeine Eingabe geändert werden, muss ihre vorherige gelöscht werden, sofern sie vorhanden ist. Dies wiederum gestaltete sich noch ein wenig komplizierter, da z.B. Änderungen des Missionstyps dafür sorgen, dass bestimmte Eingaben nicht mehr möglich sein sollen. Zusätzlich sollten bestimmte Teile der Eingabe erst erscheinen, sobald die vorherige Eingabe getätigt war. Hier ergibt sich dieselbe Problematik: Sollten Änderungen vorgenommen werden müssen eventuell einige Teile der Eingabe wieder verschwinden (Beispiel: Bei Angriff dürfen keine Ressourcen mitgenommen werden, bei Transport hingegen schon.)
Durchgeführt wurden die Effekte mittels jQuery Methoden, welche intern CSS-Effekte ausführen:

 $("#ress-choice").slideUp("fast");

Um die Zugriffe mittels jQuery möglichst einfach zu gestalten, wurden betreffende HTML-Elemente mit Klassen, bzw. IDs ausgestattet. Hier jetzt alle Dinge die zu beachten waren aufzuzählen würde den Rahmen sprengen, daher exemplarisch folgendes Szenario:
User wählt Missionstyp "Transport", sucht sein Flotte aus, gibt einen Zielplaneten ein und wählt Schiffe und Ressourcen aus. Die einzelnen Menüs haben sich alle per jQuery slide-Effekten geöffnet, welche an einem umgebenden div-Element jeweils aufgerufen wird. Daraufhin möchte der Nutzer doch lieber Missionstyp "Angriff" wählen und eine neue Eingabe machen (ohne die Seite neu zu laden). Also klickt er erneut auf "Transport" um den Button für "Angriff" freizuschalten. Hier passiert bereits folgendes: Alle unterhalb dieses Buttons liegenden Teilbereiche verschwinden, sämtliche Eingaben in Textfelder werden gelöscht und alle versteckten input-felder in der Form werden entfernt. Nun ist die Seite wieder wie zu Beginn und der User kann seine neue Eingabe durchführen.

Deine Flotten


Seite: Deine Flotten

Vorüberlegungen

Auf dieser Seite soll der User eine Übersicht seiner Flotten bekommen. Dazu soll ersichtlich sein, welche eigene Flotten auf Mission sind und welche fremden Flotten auf dem Weg zu einem seiner Planeten sind. Außerdem soll hier die Möglichkeit bestehen Fracht zu entladen und Missionen abzubrechen. Die Zeitangaben sollen relativ sein am besten auch live runterzählen (letzeres leider nicht mehr umgesetzt).

Umsetzung

Die Umsetzung dieser Seite gestaltete sich relativ simpel denn es mussten lediglich die Datenfelder der Flotten geschickt ausgelesen werden, da die kompletten Informationen über den Flug im Model der Flotte abgespeichert sind.
Es gibt im Endeffekt 3 Abschnitte in denen man stationierte, eigene Flotten auf Mission und ankommende Flotten sehen kann. Sollten Flotten Fracht geladen haben, wird ein Button angezeigt, welcher einen AJAX-Request auslöst, der, wenn es intern keine Probleme gegeben hat die Seite neu lädt um die Werte für den Planeten zu aktualisieren.
Ein weiteres Feature ist der Abbruch einer Mission, wo ebenfalls durch Drücken eines entsprechenden Knopfes ein AJAX-Request gesendet wird, der für ein Neuladen der Seite sorgt. Die Flotte deren Mission abgebrochen wurde ist anschließend sowohl im Bildschirm für eigene, als auch für ankommende Flotten.


Page last modified on August 24, 2013, at 01:19 AM