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 |
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.
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" %> |
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.
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!
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' %> |
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'] %> |
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_childrenrendered_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 |
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.
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.
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.
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.
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.











