Auf Mäusejagd mit Apotomo!

Erschöpft und mit halbgeschlossenen Augen blicken Sie auf Ihren Monitor, dutzende Fenster verschiedenster Grössen tummeln sich vor einem Heer an Icons, und inmitten dieses digitalen Labyrints eine kleine schwarze Maus, die flink zwischen Menüs, Fenstern und Ordner-Symbolen herumflitzt.

Mit verträumtem Blick folgen Sie dem Maus-Cursor, Ihre Augen lassen nicht mehr ab von diesem kleinen flinken Racker, der mühelos durch dieses virtuelle Chaos springt. Wie schön wäre es, auf eine spannende Mäusejagd zu gehen, wie ein echter Jäger, mit Speerspitze und Fallen, und aufregenden Abenteuern mit gefährlichen kleinen Nagetieren…

Die Mäusejagd ist zu verlockend, als dass Sie davon ablassen könnten. Die eigentliche Fingerübung für Ihr popliges Browser-Spiel soll darin bestehen, es nicht mit reinem JavaScript zu implementieren, sondern mit Apotomo, einer Widget-Schicht für Ruby on Rails.

Apotomo?

Apotomo stellt Ihnen Komponenten – Widgets – bereit, die sich wie kleine Rails Controller verhalten, aber von denen beliebig viele auf einer Seite verwendet werden können und diese zudem noch per Events steuerbar sind. Perfekt also für eine Rich Client App, oder eben ein Browserspiel.
Ein dunkler Wald, und eine Bärenfalle.

Noch im Halbschlaf erstellen Sie ein neues Rails Projekt

projects$ rails webhunter

mit dem knurrenden Namen webhunter, dann installieren Sie das Apotomo Plugin und Cells, auf welchem Apotomo aufsetzt.

webhunter$ script/plugin install git://github.com/apotonick/cells.git
webhunter$ script/plugin install git://github.com/apotonick/apotomo.git

Eine Bärenfalle als Widget

Als erstes muss eine Falle her, da diese Jagdform, im Gegensatz zum aktiven Angriff mit Speer, wohl eher von Erfolg gekrönt sein wird. Ein Widget in Form einer überdimensionierte Bärenfalle soll auf der Jagd nach dem Mauscursor Ihr Gefährte sein, die unerbittlich zuschnappenden Stahlklauen haben Sie schon seit Rocky I fasziniert.

webhunter$ script/generate widget BearTrap charged snapped
      exists  app/cells/ 
      create  app/cells/bear_trap 
      create  app/cells/bear_trap_cell.rb 
      create  app/cells/bear_trap/charged.html.erb 
      create  app/cells/bear_trap/snapped.html.erb 
      create  test/functional/test_bear_trap_cell.rb

Nach einem beherzten Hieb auf die Enter-Taste bekommen Sie die Widgetklasse und erste View-Stubs serviert, alles sieht sehr nach einem üblichen Controller mit Views aus. A propos Controller: den brauchen Sie natürlich auch noch, um Ihre Falle in eine Seite einzuhängen.

webhunter$ script/generate controller forest undergrowth
      exists  app/controllers/ 
      create  app/controllers/forest_controller.rb
      ...

Um dem grauen Büroalltag zu entkommen, bauen Sie sich einen dunklen ForestController, der eine Action undergrowth (Unterholz) hat, in dem Sie heimtückisch Ihre Falle aufstellen. Schon jetzt hören Sie das Knacken von Tierpfoten im Unterholz, geheimnisvolle Vögel zwitschern ihr einsames Lied, leichter Nebel kommt auf.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ForestController < ApplicationController 
  include Apotomo::ControllerMethods 
 
  def undergrowth 
    use_widgets do |root| 
      trap = cell(:bear_trap, :charged, 'evil_trap') 
      root << trap 
    end 
 
    @trap = render_widget('evil_trap') 
 
    render 
  end 
end
Ihr Dunkler-Wald Controller, noch unbefleckt und ohne Fallen.

Ihr Dunkler-Wald Controller, noch unbefleckt und ohne Fallen.

Echte Widgets in Rails

Um an die Apotomo-Zauberkräfte zu gelangen, ziehen Sie das Modul Apotomo::ControllerMethods in Ihren Controller (Zeile 2). Man ist das aufregend, Sie fühlen sich schon jetzt fast wie der Jäger aus Predator.

5
6
7
8
    use_widgets do |root| 
      trap = cell(:bear_trap, :charged, 'evil_trap') 
      root << trap 
    end

Zuallererst erstellen Sie das Widget evil_trap in der Controller Action (Zeile +2). Die #cell Methode hilft Ihnen, wenn Sie ihr den Namen der Widgetklasse, den Startzustand und eine eindeutige ID übergeben, :bear_trap würde also BearTrapCell bedeuten, :charged ist die Methode, die beim ersten Aufruf gerendert würde und evil_trap ist der eindeutige Name Ihrer bösen Falle.

Nachdem Sie das Widgetobjekt erstellt haben, hängen Sie es an ein ominöses root Widget (Zeile 7), dass Ihnen freundlicherweise durch die #use_widgets Methode bereitgestellt wurde (Zeile 5).
Hiermit haben Sie ein persistentes und einzigartiges Fallen-Widget geschaffen, die Jagd ist somit eröffnet!

Eine Bärenfalle auf Schienen

Der Vollständigkeit halber werfen Sie noch einen Blick in Ihre BearTrapCell und versichern sich, dass der State :charged nichts Unvernünftiges anstellt, wie z.B. ein Feuer zu entzünden oder sonst irgendwelche Freveleien im Gebüsch anstellt.

app/cells/bear_trap_cell.rb

1
2
3
4
5
class BearTrapCell < Apotomo::StatefulWidget 
  def charged 
    render 
  end 
end

Ein Aufruf an #render_widget im Controller auf das Widget evil_trap macht also nichts weiter, als die State Methode #charged einer bestimmten BearTrapCell Instanz auszuführen, die Methode ihrerseits rendert einfach nur ihr View (Zeile 3).

10
@trap = render_widget('evil_trap')

Um die Falle nun noch im Unterholz zu platzieren, rendern Sie Ihr fieses Widget in eine Instanzvariable des Controllers (Zeile 10).

Da Sie nun das gerenderte Markup in der Hand haben, können Sie es an einen beliebigen Ort im undergrowth.html.erb View setzen.

/app/views/forest/undergrowth.html.erb

1
2
3
<div id="forest"> 
  <%= @trap %> 
</div>

Als Sie nun von Ihrem Hochsitz in den Wald spähen, sehen Sie das gerenderte Standard-View des Fallen-Widgets in Ihrer undergrowth Action.

Ein etwas komisch anmutendes Fallen-Widget im dunklen Wald.

Ein etwas komisch anmutendes Fallen-Widget im dunklen Wald.

Mit Apotomo können Sie also kleine Mini-Controller, oder Widgets, bauen und diese in bestehende echte Controller einbetten. Hierbei legen die Widgets ein an Controller angelehntes Verhalten an den Tag, sie haben Actions („states“) und können Views rendern.

Eine interaktive Falle

Ein dunkler Wald, der Geruch von Tannen und Moos liegt Ihnen in der Nase, in der Ferne klopft ein Specht. Tief atmen Sie die Luft ein, Sie können es bereits wittern, das kleine Nagetier, das dort im Unterholz sein Unwesen treibt. Doch sollten Sie schleunigst das langweilige Standard-View Ihrer Falle gegen das Konterfei einer furchteinflössenden Bärenfalle eintauschen, sonst bricht Ihr Opfer beim dessen Anblick allenfalls in schallendes Gelächter aus.

app/cells/bear_trap/charged.html.erb

1
<%= image_tag "bear_trap_charged.png" %>
Das sieht nach Ärger aus.

Das sieht nach Ärger aus.

Ja, blanker Stahl.

OnMouseover – und die Falle schnappt zu!

Ihre Mausefalle sieht jetzt zwar toll aus, es fehlt aber ein Auslösemechanismus und ein entsprechender Zustand für das Bärenfallen Widget, wenn die Falle zugeschnappt ist.

Nun, wofür wurde der :onMouseover Event erfunden? Das ist doch genau das, was Ihre Falle braucht. Sie erweitern also das charged View.

app/cells/bear_trap/charged.html.erb

1
<%= image_tag "bear_trap_charged.png", :onMouseover => trigger_event(:mouseAlarm) %>

Sollte die Maus die Falle betreten, löst das einen Event vom Typ :mouseAlarm aus, dafür sorgt die Helper Methode #trigger_event.

Der Event wird per AJAX an Rails gemeldet und Apotomo sorgt dafür, dass der Event vom auslösenden Widget, der Bärenfalle, bis ganz hoch zum obersten root Widget blubbert. Ihre Mause- oder Bärenfalle hängt direkt am root Widget, also ist das nicht wirklich spektakulär, dennoch freuen Sie sich über das Blubbern.

Ausser Firebug fängt hier noch niemand was.

Ausser Firebug fängt hier noch niemand was.


Als Sie jetzt die Seite neu laden und mit der Maus über die Falle fahren, da passiert …nichts. Zwar wird der AJAX-Request ausgelöst, aber die Seite bleibt gleich.

Fang die Maus, äh, den Event!

Wenn Sie jetzt also mit der Maus über die Falle fahren und danach trächtigen, das arme kleine Tierchen in sein grausame Verderben zu schicken, dann triggert Apotomo zwar einen Event und dieser blubbert wie eine Blase nach oben, aber ausser oben im root Widget zu zerplatzen (eigentlich wird er einfach nur ignoriert, aber das ist fast schlimmer), geschieht nicht wirklich viel. Es fehlt natürlich ein Event Jäger, der sein Interesse für diesen Event bekundet und im Bedarfsfall knallhart handelt – und die Falle zuschnappen lässt.

app/controllers/forest_controller.rb

5
6
7
    use_widgets do |root| 
      trap = cell(:bear_trap, :charged, 'evil_trap') 
      trap.respond_to_event(:mouseAlarm, :with => :snapped)

In Ihrer erweiterten Version des ForestControllers nehmen Sie das Bärenfallen Widget und hängen solch einen Beobachter mit Hilfe #respond_to_event daran an (Zeile 7). Da der von #trigger_event ausgelöste Event aus dem Browser von der evil_trap hoch zu root blubbert, kommt er auf jeden Fall an diesem Beobachter vorbei. Dieser „sieht“ den Event und lässt die Falle zuschnappen, indem er die evil_trap in einen neuen Zustand :snapped schickt.

Widgets sind tödliche Zustandsmaschinen

Gespannt fahren Sie ein weiteres Mal mit der flinken Maus über die Falle, doch wieder passiert nichts. Was ist denn nun schon wieder falsch? Ach natürlich, Sie haben noch vergessen, der Bärenfalle diesen Zustandsübergang zu erlauben. Ohne definierte Übergänge bleibt das Widget nämlich immer in seinem Startzustand, der in #cell(...) festgelegt wurde.

app/cells/bear_trap_cell.rb

1
2
3
class BearTrapCell < Apotomo::StatefulWidget 
 
  transition :from => :charged, :to => :snapped

Sie definieren mit der Klassenmethode #transition und den beiden Parametern :from und :to einen Übergang vom Zustand :charged in :snapped. Ihr mörderisches Vorhaben verfolgend erweitern Sie im gleichen Atemzug noch die Statemethode #snapped. Diese soll erstmal nur per JavaScript eine Meldung darüber ausgeben, dass sie jetzt eigentlich zugeschnappt wäre.

app/cells/bear_trap_cell.rb

1
2
3
4
5
6
7
8
9
10
11
class BearTrapCell < Apotomo::StatefulWidget 
 
  transition :from => :charged, :to => :snapped 
 
  def charged 
    render 
  end 
 
  def snapped 
    render :js => 'alert("gotcha!")' 
  end

Findet also ein Übergang von :charged nach :snapped statt, wird die Statemethode #snapped ausgeführt, die dann per render :js eine Alarmmeldung in die Seite injiziert. Na ob das klappt? Ein weiteres Mal schicken Sie die arme Maus in die teuflische Bärenfalle, und sehen: es klappt!

Eine sprechende Falle. Wo gibt’s denn sowas?

Eine sprechende Falle. Wo gibt’s denn sowas?

Nun, mit einer lächerlichen Textmeldung ist noch lange keine Maus gefangen. Eine zuklaffende Kralle, die mit einem harten metallischen „Klack!“ gnadenlos Ihre Beute festhält, das ist das was Sie haben wollen.

Statt ungefährlichem JavaScript muss Ihre #snapped Methode also eine zugeschnappte Falle rendern.

app/cells/bear_trap_cell.rb

9
10
11
  def snapped 
    render 
  end

Diese #render Anweisung sucht nach einem View snapped.html.erb, welches Sie kurzerhand anlegen.

app/cells/bear_trap/snapped.html.erb

1
  <%= image_tag 'bear_trap_snapped.png' %>
Klack!

Klack!


Erwartungsgemäss schnappt die Falle jetzt zu, als Sie mit der Maus darüberfahren. Klack!

Nochmal zum mitschreiben

Also: Sie haben mit #render_widget Ihr Widget in eine Controller Action eingebettet. Ein Widget sieht genauso aus wie ein Controller, mit Methoden und dazugehörigen Views. Im View haben sie #trigger_event verwendet, um einen Event an Rails bzw. Apotomo zu vermelden.

Eine Reaktion auf den Event haben Sie mit #respond_to_event eingerichtet. Wird der Event erwischt, senden Sie Ihre Falle in einen weiteren Zustand. Der neue Zustand ist wieder einfach nur eine Methode, die ein View rendert. Das neue View ersetzt dann automatisch das View des vorherigen Zustands in der Seite. Das ist… cool.

Käse in die Falle: zusammengesetzte Widgets

Seit Stunden liegen Sie auf der Lauer. Mit flachem Atem blicken Sie unentwegt in Richtung Falle, eine leichte Halsstarre macht sich bereits bemerkbar. Um Sie herum wird es dunkel.

Irgendwie will Ihnen keine Maus in die Falle gehen. Und da fällt es Ihnen wie Schuppen aus den Haaren: Mäuse lieben Käse!

Sie beschliessen, ein Stück Käse in Form eines eigenständigen Widgets in das Fallen Widget zu legen. Durch das Kapseln als eigenes Widget nehmen Sie möglichst viel Wissen über Käse aus der Falle, diese soll ja eigentlich auch nur wissen, dass irgendein Köder in ihr liegt. Ob dieser nach Käse riecht oder es sich dabei um ein Stück zart schmelzender Schokolade handelt, ist einer handelsüblichen Bärenfalle normalerweise ziemlich egal.

Das Käse Widget soll aus einem Zustand bestehen, nämlich genau dem, der ein leckeres Stück Käse darstellt.

webhunter$ script/generate widget cheese smelling 
      create  app/cells/cheese_cell.rb 
      create  app/cells/cheese/smelling.html.erb

Manchmal haben Generatoren in Rails doch eine Daseinsberechtigung. Sie erstellen sich ein CheeseCell Widget, dessen einziger Zustand :smelling besitzt ein View, das nur den Köder darstellt.

app/cells/cheese/smelling.html.erb

1
<%= image_tag "cheese.png" %>

Unglaublich spannend.

Nun öffnen Sie wieder den ForestController und stecken das Stück Käse in die Falle, indem Sie den Widgetbaum erweitern.

4
5
6
7
def undergrowth 
    use_widgets do |root| 
      trap = cell(:bear_trap, :charged, 'evil_trap') 
        trap << cell(:cheese, :smelling, 'piece_of_cheese')

Durch die Zuweisung in Zeile 7 haben Sie dem evil_trap Widget ein Kind angehängt.

Ja, so schnell kann das gehen!

So ein Käse

Das Kind ist natürlich das CheeseCell Widget mit dem Startzustand :smelling, als eindeutige ID nennen Sie es piece_of_cheese.

Nun müssen Sie nur noch das View der Falle erweitern, damit dieses auch den Köder darstellt.

app/cells/bear_trap/charged.html.erb

1
2
<%= image_tag "bear_trap_charged.png", :onMouseover => trigger_event(:mouseAlarm) %>
<%= rendered_children['piece_of_cheese'] %>

Das sieht doch verlockend aus.

Das sieht doch verlockend aus.

Entzückt stellen Sie fest, dass sich Ihre Falle als äussert attraktiv herausstellt, wenn erst mal ein Stück Käse darinliegt.

Sie können also über @rendered_children rendered_children auf bereits gerenderte Kinderwidgets zugreifen, und somit Kinder in den Views der Eltern platzieren.

Der Vollständigkeit halber muss natürlich auch das View der zugeschnappten Falle erweitert werden – es könnte ja zum Beispiel sein, das tatsächlich ein Bär die Falle auslöst, dieser aber keinen Käse mag und dann zusammen mit einem Stück Käse in der Falle steckt.

app/cells/bear_trap/snapped.html.erb

1
2
<%= image_tag 'bear_trap_snapped.png' %>
<%= rendered_children['piece_of_cheese'] %>

Angriff der hungrigen Maus

Es liegt nun also Käse in der aufgestellen Falle. Sobald die Maus in die Falle tappt, schnappt diese zu, der Käse bleibt aber darin liegen. „Was für ein Unsinn!“ denken Sie sich, eigentlich müsste ja der Käse verschwinden, die Falle aber offenbleiben, denn eine Maus wird mit ihrem Fliegengewicht wohl kaum eine Bärenfalle auslösen

Die Käse-Wegfress Logik gehört weder in das Käsewidget, noch in die Falle. Ein neues Widget, ein Mauswidget, muss her.

$ script/generate widget mouse lurking eating
      exists  app/cells/
      create  app/cells/mouse
      create  app/cells/mouse_cell.rb
      create  app/cells/mouse/lurking.html.erb
      create  app/cells/mouse/eating.html.erb
      create  test/functional/test_mouse_cell.rb

Die Maus soll, wie die Falle auch, an root hängen, also direkt im Wald. Hierzu müssen Sie wieder an den #use_widgets Block im Controller ran, um die Maus in ihrem natürlichen Lebensraum zuzuführen.

app/controllers/forest_controller.rb

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  def undergrowth
    use_widgets do |root|
      trap = cell(:bear_trap, :charged, 'evil_trap')
        trap << cell(:cheese, :smelling, 'piece_of_cheese')
      #trap.respond_to_event(:mouseAlarm, :with => :charged)
      root << trap
 
      root << cell(:mouse, :lurking, 'hungry_mouse')
    end
 
    @trap   = render_widget('evil_trap')
    @mouse  = render_widget('hungry_mouse')
 
    render
  end
So sieht Ihr momentaner Widget Baum aus.

So sieht Ihr momentaner Widget Baum aus.

Zuallererst haben Sie den Beobachter auskommentiert, der die Falle zuschnappen lässt (Zeile 8). Darum wollen Sie sich später kümmern, ausserdem haben Sie auf einmal Angst, Sie könnten die süsse Maus verletzen.

Dann hängen Sie das neue Mauswidget in den Widget Baum, und zwar im Startzustand :lurking (Zeile 11). Um die Maus noch in das Controllerview einzubauen, rendern Sie es mit #render_widget (Zeile 15).

app/cells/mouse/lurking.html.erb

1
<%= image_tag "mouse.png" %>

Nachdem Sie das View des :lurking Zustandes noch etwas verschönert haben, stehen sich jetzt tatsächlich die Duellisten Maus und Falle gegenüber.

Einer gegen Einen.

Einer gegen Einen.

Aufsteigende Events

Um jetzt den Käse aus der Falle zu stehlen, muss die Maus nur noch den :mouseAlarm Event abfangen und den Käse fressen. Hierzu erweitern Sie wieder den #use_widgets Block im Controller.

app/controllers/forest_controller.rb

4
5
6
7
8
9
10
11
12
  def undergrowth
    use_widgets do |root|
      trap = cell(:bear_trap, :charged, 'evil_trap')
        trap << cell(:cheese, :smelling, 'piece_of_cheese')
      root << trap
 
      root << cell(:mouse, :lurking, 'hungry_mouse')
      root.respond_to_event(:mouseAlarm, :with => :eating, :on => 'hungry_mouse')
    end

Sie hängen also einen neuen Beobachter an root (Zeile 11) und lesen sich diese Zeile einfach laut vor.

Root, falls Du einen Event vom Typ :mouseAlarm siehst, dann reagiere darauf, indem Du das Widget hungry_mouse in den :eating Zustand versetzt.“.

Sie konnten diesen Beobachter nicht direkt ans Mauswidget hängen. Warum? Na weil der :mouseAlarm Event ja von der Falle ausgelöst wird, und dann hoch zu root blubbert. Auf seinem Weg nach oben kommt er aber gar nicht bei der Maus vorbei, also muss root aushelfen.

Und endlich: Statefulness!

Sie beschliessen, statt den Käse verschwinden zu lassen, lieber die Anzahl der gefressenen Käsestücke im Mauswidget zu zählen, das mittlerweile schon ganz schön viele Zeilen Code enthält.

app/cells/mouse_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MouseCell < Apotomo::StatefulWidget
  transition :from => :lurking, :to => :eating
  transition :in => :eating
 
  def lurking
    @cheese_count = 0
    render
  end
 
  def eating
    @cheese_count += 1    
    render :view => :lurking
  end
end

Es wäre doch lustig, wenn die Maus mit jedem Stückchen gefressenen Käse dicker werden würde! Dies implementieren Sie, indem Sie das View von :lurking erweitern.

app/cells/mouse/lurking.html.erb

1
<%= image_tag "mouse.png", :width => 90 * (@cheese_count+1)  %>

Die Bildbreite wird also bei jedem Rendern neu berechnet, und mit jedem gefressenen Käse wird die Maus fetter.

Fette Maus.

Fette Maus.

Also, was geht hier genau vor?

Von Zustand zu Zustand

Wenn Sie die Maus in die Falle schicken, löst das den altbekannten :mouseAlarm Event aus. Dieser wird abgefangen und schickt das hungry_mouse Widget in den :eating Zustand. Da dies ein Zustandsübergang ist, der von aussen angestossen wird (nämlich eben durch den #respond_to_event Beobachter), muss dieser Übergang auch erlaubt werden (Zeile 2).

Danach bleibt die Maus am Fressen, im Zustand :eating. Ein weiterer Event schickt das Widget also von :eating in :eating, auch das muss erlaubt sein (Zeile 3).

Käse zählen

Der Käsezähler wird im Startzustand :lurking erstmal initialisiert und auf Null gesetzt, schliesslich hat Ihr Mäuschen noch einen leeren Magen (Zeile 6).

app/cells/mouse_cell.rb

5
6
7
8
9
10
11
12
13
  def lurking
    @cheese_count = 0
    render
  end
 
  def eating
    @cheese_count += 1    
    render :view => :lurking
  end

Wie Sie schon definiert haben, bleibt die Maus danach im :eating Zustand, und dessen Zustandsmethode wird bei jedem Event erneut ausgeführt. Die Methode kümmert sich um das Erhöhen des Käsezählers (Zeile 11).

Ist dieser auf dem neusten Stand, wird die View des :lurking Zustands gerendert und eine immer fetter werdende Maus dargestellt (Zeile 12).

Statefulness und automatisches Updaten

Entzückt bemerken Sie, dass die Instanzvariable @cheese_count tatsächlich erhalten bleibt, obwohl zwischen den eigentlichen Events mehrere Requests liegen. Das sind demnach die stateful widgets, von denen dauernd die Sprache ist.

Es erübrigt sich, zu Erwähnen, dass sich jedes Widget, also auch unsere fettsüchtige Maus, automatisch selbst auf der Seite per AJAX updatet. Oder haben Sie schon eine Zeile JavaScript geschrieben, seit Sie dieses bahnbrechende Maus-Spiel entwickeln?

Miteinander kommunizierende Widgets

Eigentlich wäre es nur konsequent, dass eine fette, schwere Maus auch irgendwann mal die Falle zuschnappen lässt, da ihr Gewicht zum Zeitpunkt X ausreichend ist, den Auslöser auch wirklich zu betätigen.

Die Falle muss also irgendwie herauskriegen, ob die Maus schwer genug ist. Sie sollten den Beobachter der Falle wieder ins Spiel bringen.

app/controllers/forest_controller.rb

4
5
6
7
8
9
def undergrowth
    use_widgets do |root|
      trap = cell(:bear_trap, :charged, 'evil_trap')
        trap << cell(:cheese, :smelling, 'piece_of_cheese')
      trap.respond_to_event(:mouseAlarm, :with => :snapped)
      ...

Nicht nur die Maus, sondern nun auch noch die Falle beobachten jetzt den :mouseAlarm Event.

Ja- es ist absolut in Ordnung, mehrere Beobachter für einen Event zu haben.

Die ist noch nicht fett genug!

Die ist noch nicht fett genug!

Der Zustand :snapped der Bärenfalle sollte jetzt darüber entscheiden, ob es sich lohnt, zuzuschnappen oder doch lieber auf fettere Beute zu warten.

Wenn Sie momentan nämlich den Zeiger über die Falle bewegen, schnappt diese zu, und die Maus wird ein bisschen dicker. Die Maus soll aber richtig fett sein, damit die Falle ausgelöst wird, mindestens 3 Käsestücke muss Sie gefressen haben, entscheiden Sie.

app/cells/mouse_cell.rb

1
2
3
4
5
6
7
8
9
10
11
class BearTrapCell < Apotomo::StatefulWidget
  transition :from => :charged, :to => :snapped
 
  def charged
    render
  end
 
  def snapped
    jump_to_state :charged unless root.find_by_id('hungry_mouse').cheese_count >= 3
    render
  end

Die Bärenfalle wird also bei jedem :mouseAlarm in :snapped geschickt. Sie bleibt allerdings nur in diesem Zustand, wenn der Käsezähler der Maus wirklich grösser als 3 ist (Zeile 9).

Ansonsten springt sie mit der Methode #jump_to_state zurück nach :charged und bleibt offen für fette Beute.

Natürlich funktioniert das so noch nicht ganz, die Maus muss noch auf #cheese_count antworten.

app/cells/mouse_cell.rb

1
2
3
4
5
class MouseCell < Apotomo::StatefulWidget
  transition :from => :lurking, :to => :eating
  transition :in => :eating
 
  attr_reader :cheese_count

Jetzt wird nicht nur die Maus fetter, wenn Sie Käse aus der Falle stiehlt, auch die Falle regt sich und fragt bei der Maus nach, ob diese vielleicht zufällig schwer genug ist.

Die wird ja immer fetter!

Die wird ja immer fetter!


Und tatsächlich, es funktioniert! Beim vierten Mal schnappt die Falle zu.

Für heute reicht’s!

Schön wäre es noch, wenn der Käse und die Maus verschwinden würden. Denn der Käse ist gefressen, und die fette Maus macht sich aus dem Staube, wenn die Bärenfalle mit einem lauten „Klack!“ zuschnappt, oder haben Sie schonmal ‘ne Maus gesehen, die in eine Bärenfalle passt?

Auch stört Sie ein bisschen die Abhängigkeit, die Sie in der Bärenfalle geschaffen haben – diese weiss ja jetzt Bescheid über die hungry_mouse und befragt das Mauswidget in der #snapped Zustandsmethode, das sollte bei unabhängigen Komponenten nicht der Fall sein.

„Aber das kann man mit Apotomo bestimmt auch anders machen, doch für heute reicht es!“, sagen Sie sich lehnen sich zurück und freuen sich darüber, dass in Ihrem Spiel noch keine einzige Maus verletzt wurde.

Leave a Reply