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 - Spieler Allianzen Und Nachrichten Allianzen

Allianzen und Ränge

René Helmke

Problemstellung

Unsere Aufgabe war es, Spielerallianzen zu implementieren. Konkret sind Allianzen freiwillige Zusammenschlüsse von Spielern mit dem Ziel der Vorteilsbeschaffung. Spieler sollten Allianzen erstellen, verwalten und beitreten sowie Allianznachrichten an alle Mitglieder verschicken können. Wir selbst haben uns zum Ziel gesetzt, ein voll funktionsfähiges, modular aufgebautes Allianzsystem mit Rollenverteilung zu implementieren. So sollten Allianzgründer möglichst viel Freiheit bei der Gestaltung der Hierarchie und Organisierung ihrer eigenen Reihen haben. Ebenso wie bei der Nutzerverwaltung sollten Allianzen eine Wertung bekommen, um den Wettbewerb zwischen diesen anzureizen. Zur Bewerkstelligung dieser Aufgabe war es nötig, ein Rängesystem zu konzipieren.

Planung

Datenbankebene


Abb.1: Relationen zwischen Nutzern, Allianzen und Rängen

Ein Nutzer kann immer nur in einer Allianz sein. Eine Allianz besteht an sich nur aus einem Namen, einer Beschreibung und einem Highscore. Jede Allianz kann seine eigenen Ränge erstellen und verwalten. Einem Rang sind mehrere Allianzmitglieder zugeteilt. Ein Rang besteht aus einem Namen und einer Menge von Attributen, die Auskunft über die Privilegien eines Mitgliedes innerhalb der zugehörigen Allianz geben:

  • is_founder: Markiert den einzigartigen Rang des Allianzgründers
  • standard: Markiert den Rang, zu dem neue Mitglieder hinzugefügt werden sollen.
  • can_change_description: Können Mitglieder dieses Ranges die Allianzbeschreibung ändern?
  • can_edit_ranks: Können Mitglieder dieses Ranges Ränge verwalten?
  • can_kick: Können Mitglieder dieses Ranges andere Mitglieder entfernen?
  • can_invite: Können Mitglieder dieses Ranges andere Personen einladen?
  • can_massmail: Können Mitglieder dieses Ranges Rundnachrichten schicken?

Auf Basis der oben genannten Privilegien ohne standard und is_founder lassen sich 25=32 einzigartige Ränge in die Allianz einpflegen.

Applikationsebene


Abb.2: Ablauf einer Allianzerstellung

Erklärung:

  • Erstellt der Nutzer eine neue Allianz, wird geprüft, ob dieser Name noch vorhanden ist
  • Danach wird geprüft, ob der Nutzer noch keiner anderen Allianz zugeordnet ist
  • Ist beides der Fall, wird die Allianz erstellt
  • Nun werden zwei initiale Ränge erstellt:
    • Der Standardrang
      • Alle Nutzer werden bei Hinzufügung diesem Rang zugeordnet
      • Alle Attribute bis auf standard sind auf false gesetzt.
      • Der Standardrang soll später neu definierbar sein
    • Der Gründerrang
      • Nur der Gründer wird diesem Rang zugeordnet
      • Alle Attribute bis auf standard sind auf true gesetzt.
      • Die Attribute des Gründerranges sind nicht veränderbar
      • Es kann immer nur ein User diesen Rang besitzen
  • Schließlich wird der Nutzer der Allianz hinzugefügt und der entsprechende Rang zugeteilt

Implementierung

Model

Alliance

Zuerst mussten die Beziehungen zwischen Allianzen, Rängen und Usern definiert werden:

  has_many :ranks
  has_many :users

Entsprechend im User-Model:

  belongs_to :alliance

Die Definierung auf Seite der Ränge soll später erklärt werden.

Im nächsten Schritt musste garantiert werden, dass Allianznamen einzigartig sind:

  validate :is_alliance_name_taken, on: :create
  [...]
  #checks if alliance name is already taken
    def is_alliance_name_taken
      if Alliance.exists?(:name.downcase => name.downcase)
        errors.add :name, "is already taken"
      end
    end

Außerdem musste für Datenintegrität gesorgt werden. Wir haben uns dafür entschieden, dass es ausreicht, wenn die Allianz einen Namen gesetzt hat:

  validates_presence_of :name

Nach der Erstellung der Allianz sollten, wie oben in Abbildung 2 dargestellt, zwei vordefinierte Ränge erstellt werden. Dieses wurde über eine Callbackmethode realsiert:

  after_save :create_default_ranks, on: :create
  [...]
  private
  def create_default_ranks
    self.ranks.create(:name=>"Anwärter",:standard=>true)
    self.ranks.create(:name => "Oberhaupt",
                      :can_kick=>true,
                      :can_massmail=>true,
                      :can_edit_ranks=>true,
                      :can_invite=>true,
                      :is_founder=>true,
                      :can_disband=>true,
                      :can_change_description => true)
  end

Im weiteren Verlauf haben wir eine Reihe von Methoden implementiert die ich allerdings, mit Ausnahme von zweien, nicht weiter beleuchten werde:

  • set_founder(newfounder,oldfounder=nil): setzt oder tauscht den Rang des Gründes mit dem eines anderen Allianzmitgliedes.
  • add_score(score): Fügt eine Anzahl an Punkten zu der Allianzhighscore hinzu.
  • add_user(user): Fügt einen Nutzer zu einer Allianz hinzu. add_score(score) wird intern aufgerufen.
  • remove_user(user): Entfernt einen Nutzer aus der Allianz und entzieht seinen Rang. add_score(score) wird intern mit einem negativen Wert aufgerufen.
  • change_default_rank(rank): Setzt einen anderen Rang als Standardrang.
  • change_user_rank(user,rank): Ändert den Rang eines Nutzers.
  • set_description(description): Ändert die Beschreibung der Allianz.
  • send_mass_mail(user, subject, body): Sendet im Auftrag eines Nutzers eine Rundnachricht an alle Allianzmitglieder
  • permission?(user,action): Gibt Auskunft über die Privilegien eines bestimmten Mitgliedes.

Es stellte sich bei add_score(score) die Frage, wie sich genau der Highscore der Allianz berechnen lässt. Wir haben lange überlegt und uns schließlich für den einfachsten Weg entschieden: Der Highscore der Allianz berechnet sich aus der Summe aller Nutzerwertungen. Entsprechend musste die bei der Nutzerverwaltung implementierte, gleichnamige Methode ergänzt werden. So sollten bei dem Hinzufügen oder Abziehen von Punkten zur Nutzerhighscore die Änderung auf die Allianz des Nutzers populiert werden:

user.rb

  def add_score value
    if self.alliance!=nil
      self.alliance.add_score(value)
    end
    update_attribute(:score, score + value)
  end

Beim Hinzufügen oder Entfernen eines Nutzers zu einer Allianz musste dann nurnoch der momentane Highscore des Nutzers auf den Highscore der Allianz addiert oder subtrahiert werden.

Da wir bei der Implementierung des Controllers bezüglich der Übersichtlichkeit starke Probleme aufgrund der vielen Kontrollstrukturen bekamen, haben wir entschlossen, in die Allianz ein Gerüst von Methoden zu implementieren, die Aussagen über die Privilegien eines gegebenen Mitgliedes bezüglich bestimmter Aktionen fällt. Die Methode permission?(user,action) ist die universale Schnittstelle zu diesem Gerüst. Man stelle sich dieses Gerüst von Methoden als Pool von Voraussetzungen für die Durchführbarkeit einer Aktion, wie dem Bearbeiten von Rängen, vor. permission?(user,action) führt diesen Pool von Voraussetzungen zusammen und entscheidet je nach Aktion, welche Voraussetzungen des Nutzers und dessen Rang gegeben sein müssen. Sind alle Voraussetzungen erfüllt, liefert diese Methode true zurück. Bildlich dargestellt sieht dieses wie folgt aus:


Abb.3: permission?
Rank

An dem Rank-Model mussten zuerst die Beziehungen zwischen Nutzern und Allianzen eingepflegt werden:

  belongs_to :alliance
  has_many :users

Und im User-Model:

  belongs_to :rank

Gleich den Allianzen musste eine Validierung für die Einzigartigkeit es Rangnamens implementiert werden. Diese Einzigartigkeit bezieht sich nur auf die dem Rang zugehörige Allianz:

  validate :is_rank_name_taken, on: :create
  [...]
  #check if rank name is taken
  def is_rank_name_taken
    @ranks=self.alliance.ranks
    @ranks.each do |r|
      if r.name.downcase==self.name.downcase
        errors.add :name,"already exists in your alliance"
      end
    end	
  end

Für unsere Zwecke brauchten wir keine weiteren Methoden in das Rank-Model einpflegen.

Controller

Die Controller mussten hauptsächlich vor Fremdeingriff abgesichert werden. So werden mit Hilfe der Methode permission?(user,action) nur die Anfragen von Nutzern bearbeitet, die auch tatsächlich die Erlaubnis dazu besitzen. Für die Methoden aus den Models, die der Nutzer selbst ansteuern kann, mussten entsprechende Controllermethoden und Routen eingerichtet werden. Eine kurze Übersicht über ergänzte Methoden:

  • POST /alliances/1/edit/send_mail: Massenmails
  • POST /alliances/1/edit/change_founder: Gründer tritt seinen Rang ab
  • POST /alliances/1/edit/change_default_rank: Standardrang ändern
  • PUT /alliances/1/edit/change_user_rank: Nutzerrang ändern
  • POST /alliances/1/edit/remove_user: Nutzer aus der Allianz entfernen
  • POST /alliances/1/edit/leave: Allianz verlassen

View

Bei den Allianzen haben wir uns entschieden, die meisten Views von Grund auf neu zu schreiben. Wir haben uns gewünscht, dass jede View der Allianz dynamisch nur die Felder, Informationen und Buttons anzeigt, die das Allianzmitglied auch sehen darf. Für die Allianzeditierung wurden verschiedene Komponenten entwickelt:


Abb.4: Nutzerverwaltung


Abb.5: Rängeverwaltung


Abb.6: Beschreibung ändern


Abb.7: Rundmails verschicken


Abb.8: Gründerbuttons

Hierbei ist zu beachten, dass jede Komponente nur die Optionen für den Nutzer anzeigt, die auch gültig sind. So kann man sich beispielsweise nicht selbst aus einer Allianz entfernen oder seinen eigenen Rang bearbeiten. Ebenfalls können Gründer- Standardrang nicht gelöscht werden. Des Weiteren wurde bei der Editierung der Ränge darauf geachtet, dass der Gründerrang nur im Namen verändert werden kann. Dies ist in den Views sowie dem Controller abgesichert.

Bei einem gültigen Aufruf der Editierungsseite rendert der Controller nur eine View:

edit.html.erb:

  <h1> <%= @alliance.name %> bearbeiten: </h1>
  <%= render 'showUsers' %>
  <%= render 'showRanks' if @alliance.permission?(current_user,"edit_ranks") %> 
  <%= render 'changeDescription' if @alliance.permission?(current_user,"edit_ranks") %>
  <%= render 'showMassMail' if @alliance.permission?(current_user,"massmail") %>
  <%= render 'showFounderOptions' if @alliance.permission?(current_user,"destroy") %>

Wie hier zu erkennen ist, baut sich diese View je nach Privilegien des Nutzers dynamisch zusammen. Grafisch würde dieser Vorgang wie folgt aussehen:


Abb.9: Dynamischer Seitenaufbau

Anschließend haben wir noch einen Allianzüberblick implementiert:


Abb.10: Allianzüberblick

Am interessantesten an dieser View ist der untere Bereich namens "Deine Score-Anteile". Dieser stellt grafisch dar, wie viel der User selbst zu dem Highscore seiner eigenen Allianz beigetragen hat. Der Quellcode dazu sieht wie folgt aus:

<% if current_user.alliance==@alliance %>

    <div class="well">
      <strong>Deine Score-Anteile:</strong>
    </div>
    <div class="progress progress-striped">
    <% if @alliance.score!=0 %>
      <% usr=(current_user.score.to_f/@alliance.score.to_f)*100 %>
    <% else %>
      <% usr=0 %>
    <% end %>
    <% alli=100-usr %>
    <% alli=alli+1 if usr.to_i+alli.to_i!=100 %>
    <span style="font-weight: bold"><%= current_user.score.to_s+" / "+@alliance.score.to_s %></span>
    <div class="bar bar-success" style="width: <%= usr.to_i %>%;"></div>
    <div class="bar bar-info" style="width: <%= alli.to_i %>%;left:<%= usr.to_i %>%;"></div>
  </div>

<% end %>

Da die ganzen Allianzviews für den Nutzer zuerst etwas unverständlich vorkommen, haben wir Tooltips zu jeder nennenswerten Anzeige und Option hinzugefügt:


Abb.11: Tooltips

Fazit

Unser Rängesystem funktioniert grundsätzlich zufriedenstellend. Im Nachhinein bereuen wir allerdings einige Designentscheidungen bezüglich diesem. Wie bereits in meiner Präsentation angesprochen, lässt sich nicht eindeutig entscheiden, welches Mitglied mit bestimmten Privilegien ein anderes Mitglied bearbeiten darf. Dies bezieht sich insbesondere auf die Privilegien can_edit_ranks und can_kick. Wir haben über dieses Thema im nachhinein lange (auch mit erfahreneren Studenten) diskutiert und sind einerseits zu dem Entschluss gekommen, dass ein hierarchisch aufgebautes Konzept im Gegensatz zu einem rollenorientierten Konzept durchaus sinnvoller gewesen wäre. Auf der anderen Seite muss man allerdings auch überlegen, wieviel Eigenverantwortung man dem Gründer der Allianz zutrauen möchte. Der Lösungsvorschlag, den ich in meiner Präsentation angesprochen habe, stellte sich als nicht umsetzbar heraus, da wir dann hierarchische und rollenorientierte Rechteverwaltung vermischt und das System somit noch intransparenter gestaltet hätten. Des Weiteren gab es sehr zeitaufwändige Probleme bezüglich der Sicherheit dieses Systemes. So musste genau ausgearbeitet werden, welcher Nutzer was unter welchen Bedingungen mit seinen Privilegien machen durfte. Die Komplexität einer Rechteverwaltung wurde uns erst im Verlauf der Zeit bewusst.


Page last modified on August 23, 2013, at 04:16 PM