Finite State Engine - Messaging
Basierend auf dem Artikel “Pseudointelligente Gamebots” und der dort entwickelten FSE (Finite State Engine) ist nun eine Erweiterung geschrieben worden.
Es begann mit einem Email-Kontakt mit dem User wieschoo, welcher ein ganz interessantes Projekt am laufen hat: Die BotSuite.NET. Diese Suite stellt bisher Funktionen für Bilderkennung, Benutzereingaben und Ausgaben bereit. Wie gut das funktioniert kann man auf diversen Videos des Autors bestaunen Eine FSE würde da gut reinpassen, daher bat er mich drum eine solche FSE für ihn zur Verfügung zu stellen. Damit Ihr aber auch davon profitiert werde ich in wenigen Auszügen die Änderungen an der FSE erklären und am Ende das Beispiel etwas näher beleuchten.
Die State-Klasse hat sich etwas geändert. Statt der abstrakten Funktion “Execute” gibt es nun auch “Enter” und “Leave”, welche einmalig beim Ändern des States aufgerufen werden. Zudem muss jeder State “bool OnMessage(BaseEntity, Telegram)” überschrieben, die Funktion zum Handeln einer Message. Mehr zum Message-System gleich, ist nur wichtig im Hinterkopf zu behalten.
Leider ist es bei einem Message-System unabdingbar mit Entites zu arbeiten, sonst würde das Message-System keine Ahnung haben an wen die Nachricht gehen sollte. Daher ist eine BaseEntity mit einer ID und einer abstrakten HandleMessage-Funktion dazugekommen, ein Singleton einer EntityManager-Klasse stellt Funktionen wie “RegisterEntity” und “GetEntityByID” bereit.
Das eigentliche Message-System arbeitet mit sog. “Telegrams”. Ein Telegram besteht einfach aus folgenden Variablen:
public struct Telegram { public Int32 Sender; // ID des Senders public Int32 Reciever; // ID des Empfängers public Int32 Message; // ID der Message public DateTime DispatchTime; // Zeit, in der die Message abgeschickt werden sollte public Object AdditionalInfo; // Zusätzlicher Parameter |
Die Kommentare sind selbsterklärend, der Member “Message” wird später, um die Übersicht zu behalten, in einem Enum gecasted. DIspatchTime gibt die Zeit an, um verzögerte Nachrichten zu schicken. Und darum kümmert sich die Klasse MessageDispatcher.
Ebenfalls Singleton bietet die Klasse nur eine public Funktion: DispatchMessage. Diese ist aus Verständnisgründen voll abgedruckt:
public void DispatchMessage(Int32 _Sender, Int32 _Reciever, Int32 _Message, double Delay, Object _AdditionalInfo) { Messaging.Telegram Message = new Telegram(_Sender, _Reciever, _Message, DateTime.Now.AddSeconds(Delay), _AdditionalInfo); if (Delay != 0) { this.lstDelayedMessages.Add(Message); } else { DischargeMessage(Pattern.Singleton.Instance.GetEntityByID(Message.Reciever), Message); } } |
Wenn Delay gesetzt ist, so wird diese Zeit in Sekunden zur eigentlichen Dispatch-Zeit hinzugefügt und die Message in die Liste verschobenen. Bei jedem Update-Vorgang wird dann geprüft ob diese Zeit kleiner als die aktuelle Zeit ist, wenn ja wird sie losgeschickt und aus der Liste gelöscht. Eine nicht versetze Nachricht ruft direkt DischargeMessage auf:
private void DischargeMessage(BaseEntity Reciever, Telegram Message) { if (!Reciever.HandleMessage(Message)) { throw new Exception("No message handler for this message found!"); } } |
Erinnnert ihr euch dran dass jede BaseEntity HandleMessage überschreiben muss? Denn in HandleMessage wird die Message an den aktuellen State und an den globalen State weitergereicht, wenn sie nicht bearbeitet wurde wird “false” zurückgeliefert.
Damit haben wir alle Informationen um zum spassigen Teil, dem Beispiel zu gehen: Es gibt wieder mal um einen Bergbauarbeiter (Miner) und einen Barkeeper. Nach getaner Arbeit liebt der Miner es in seiner Stammkneipe ein Bier zu genießen und dazu Pizza essen. Sobald der Miner der BarState betritt (Enter) wird eine Nachricht an den Barkeeper gesendert: “Hallo Barkeeper, ich bin hier!”. Der Barkeeper begrüßt ihn ebenfalls und schiebt eine Pizza in den Ofen, mit einer verzögerten Nachricht an sich selbst sie nach 5 Sekunden wieder rauszunehmen.
Wir brauchen insgesammt 3 States: WorkState<Miner>, BarState<Miner> und WaitState<Miner>. WorkState<Miner> kann im Source nachgelesen werden, mit einer Warscheinlichkeit von 1 zu 4 verlässt der Miner die Arbeit.
BarState schauen wir uns schon genauer an:
class BarState : State { // Miner betritt die Bar public override void Enter(Miner Parent) { // Message an den Barkeeper (Sender = Miner, Barkeeper = 0x01, MessageID = 0x00, Delay = 0x00) Singleton.Instance.DispatchMessage(Parent.ID, 0x01, 0x00, 0x00, null); } public override void Run(Miner Parent) { // Der Miner genießt das Bier Utils.ConsoleHelper.Print("Drinking beer and vodka", "Miener"); } public override void Leave(Miner Parent) { } // kein Message-Handling für diesen State des Miners public override bool OnMessage(Miner Sender, FiniteStateEngine.StateEngine.Messaging.Telegram Message) { return false; } |
Das interessante ist die “Enter” Funktion des States, hier sendet der Miner an den Barkeeper (ID = 0×01) die Message 0×00 das heißt: “Hallo, ich bin hier”. Als Delay wurde 0×00 angegeben, AdditionalInfo ist null.
Dann fehlt nur noch der MessageHandler-State des Barkeepers und wir sind fertig:
public override bool OnMessage(Barkeeper Sender, FiniteStateEngine.StateEngine.Messaging.Telegram Message) { if (Message.Message == 0x00) { Utils.ConsoleHelper.Print("Ohai little miner! Pizza is ready in 5 sec", "Barkeeper"); // Verzögerte Nachricht an sich selbst FiniteStateEngine.Pattern.Singleton.Instance.DispatchMessage(Message.Reciever, Message.Reciever, 0x01, 5, null); return true; } // Pizza ist fertig! if (Message.Message == 0x01) { Utils.ConsoleHelper.Print("Pizza is ready! Yumm", "Barkeeper"); return true; } return false; } |
Sobald der Barkeeper Nachricht 0×00 bekommt, wirft er die Pizza in den Backofen und schreibt sich selbst eine Nachricht mit ID 0×01 mit einer Verzögerung von 5 Sekunden. Wenn er nun die Nachricht bekommt sind alle glücklich und unsere FSE funktioniert.
Download:Finate State Engine - Messaging (134)
Greez Easy
Genial! Danke für deine Mühe.
Der arme Miner kann aber am nächsten Morgen nicht zur Arbeit erscheinen, da er am Ende nur noch trinkt
Kein Problem
Du kannst ja eine Promillegrenze einbauen
Oder dass er per Handy seine Frau bittet ihn abzuholen (Message 0×02), die Hupt sobald sie vor der Bar steht (Message 0×03)
Message 0×04:
Die FSE ist nun in der BotSuite mit drin. Hat so lange gedauert, da ich selbst erst einmal den Quelltext verdauen musste. Backlink auf deine Seite ist ebenfalls gesetzt.
Freut mich zu hören, danke