Langtons Ameisen als Windows WPF Anwendung umsetzen

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
self.made_man
User
Beiträge: 7
Registriert: Mittwoch 11. Dezember 2013, 19:37

Ich habe mal etwas mit den Langtonschen Ameisen gespielt. Jetzt möchte ich das Programm statt in einer Textkonsole in einem "echten Fenster" mit der Windows WPF und IronPython umsetzen.

Bin für jeden Tipp dankbar. :)

Hier der Code:

Code: Alles auswählen

import System
from System.Diagnostics import Stopwatch

MAX_STEPS = 10**7

class TPLA_world():
    def __init__(self, rule = "LR", LMT = 0):
        """
        Usage:
        xyz = TPLA_world()        # will create a world with the rule "LR"
        xyz = TPA_world("LLRR")   # will create a world with the rule "LLRR"
        xyz = TPA_world(LMT = 13) # will create a world with the rule "RLR"
        see http://en.wikipedia.org/wiki/Langton%27s_ant for more details.
        """
        self.min_x = 0
        self.max_x = 0
        self.min_y = 0
        self.max_y = 0
        if LMT > 3:
            rule = bin(LMT)[3:].replace('0', 'L').replace('1', 'R')
        self.rule = []
        for c in rule:
            if c == 'L':
                self.rule.append(3)
            elif c == 'U':
                self.rule.append(2)
            elif c == 'R':
                self.rule.append(1)
            elif c == 'N':
                self.rule.append(0)
        self.rule_deep = len(self.rule)
        self.map = {}
        self.map[(0,0)] = 0
        self.pos_x = 0
        self.pos_y = 0
        self.direction = 0          # 0 = up, 1 = right, 2 = down, 3 = left

    def make_one_step(self):
        """
        Move the ant one step forward. A world map is build, where each place the ant enters gets an integer
        value how often the ant walks over the place.
        """

        # find the new direction
        self.direction = (self.direction + self.rule[self.map[(self.pos_x, self.pos_y)] % self.rule_deep]) % 4

        #color the field
        self.map[(self.pos_x, self.pos_y)] += 1

        # move into the direction
        if self.direction == 0:
            self.pos_y += 1
        elif self.direction == 1:
            self.pos_x += 1
        elif self.direction == 2:
            self.pos_y -= 1
        else:
            self.pos_x -= 1

        #entering new ground?
        if not (self.pos_x, self.pos_y) in self.map:
            self.map[(self.pos_x, self.pos_y)] = 0
            if self.pos_x < self.min_x:
                self.min_x = self.pos_x
            if self.pos_x > self.max_x:
                self.max_x = self.pos_x
            if self.pos_y < self.min_y:
                self.min_y = self.pos_y
            if self.pos_y > self.max_y:
                self.max_y = self.pos_y

    def check_dimension(self, x = 77, y = 300):
        """For checking the printed result to fit into a standard console window."""

        return (self.max_x - self.min_x > x) or (self.max_y - self.min_y > y)

    def print_map(self):
        """Print the worls map to a Windows console."""

        for y in range(self.max_y, self.min_y - 1, -1):
            for x in range(self.max_x, self.min_x - 1, -1):
                if (x, y) in self.map:
                    point_info = self.map[(x, y)] % self.rule_deep
                    if point_info == 0:
                        System.Console.Write(".")
                    elif point_info == 1:
                        System.Console.Write("#")
                    else:
                        System.Console.Write(point_info % 10)
                else:
                    System.Console.Write(" ")
            System.Console.WriteLine()
        System.Console.WriteLine("\n{0} <= x <= {1}", self.min_x, self.max_x)
        System.Console.WriteLine("{0} <= y <= {1}\n", self.min_y, self.max_y)

# ask for ant's rule to walk
System.Console.WriteLine("Please enter the rule how to walk. A string of 'L' and 'R',")
System.Console.WriteLine("where 'L' let the ant turn left and 'R' right.")
System.Console.WriteLine("Also allowed: 'U' as an 180 degree U-turn and 'N' for no turn (keep direction).")
System.Console.WriteLine("Some nice rules are 'LLRR' or 'LRRRLLR'. Melt your CPU with LLLRR.")
System.Console.WriteLine("Create a highway with RNNU or URLRUN.")
System.Console.WriteLine("You can enter the LMT value alternatively (19 is the same as LLRR),")
System.Console.WriteLine("see http://de.wikipedia.org/wiki/Ameise_(Turingmaschine) for details.")
System.Console.WriteLine("Try 12345 to get a nice rectangle.")
System.Console.WriteLine()
System.Console.Write("rule = ")
rule_string = System.Console.ReadLine().strip()
System.Console.WriteLine()

# check the string and init ant's little world
if rule_string.isnumeric() and int(rule_string) > 3:
    world = TPLA_world(LMT=int(rule_string))
else:
    if not rule_string:
        rule_string = "error!"
    for char in rule_string:
        if not char in "LRUN":
            System.Console.WriteLine("Your string contains unknown characters other than 'L', 'R', 'U' or 'N'.")
            System.Console.WriteLine("We use the rule 'LLRR' instead.")
            rule_string = "LLRR"
            break
    world = TPLA_world(rule_string)


# create a "we are alive" message
System.Console.Write("One moment please ")  

# measure execution time
timer = Stopwatch()
timer.Start()

# the calculation
i = 0
while not world.check_dimension() and i < MAX_STEPS:
    world.make_one_step()
    i += 1
    if not i % 250000:
        System.Console.Write(".")
timer.Stop()

# print results
System.Console.Write("\n\n")
world.print_map()
if i < MAX_STEPS:
    System.Console.WriteLine("We have to abort the calculation, else the map would not fit into the window!")
System.Console.WriteLine("The little ant walked {0} steps in {1} ms.\nThis is equal to one million steps in {2} s.", i, timer.ElapsedMilliseconds, timer.ElapsedMilliseconds * 1000.0 / i)

# Wait for a key press before closing the console window. 
print "Press any key to continue..." 
System.Console.ReadKey(True)
Der eigentliche Berechnungs-Code geht bis Zeile 70 und definiert eine Klasse. Innerhalb der Klasse erzeuge ich ein Dictionary mit 2'er-Tupeln als Index und Ganzzahlen als Werten. Betritt die Ameise Neuland vergrößert sich das Dictionary. Damit ist das Konzept offen in der Dimensionierung, wenn auch (absichtlich) etwas seltsam implementiert.

Man würde vermutlich eher ein zweidimensionales Array für die Map verwenden, aber ich wollte die Leistung bei der Bearbeitung von Dictionaries messen und mit nativem C# vergleichen. Das IronPython Programm ist also eigentlich ein Leistungstest.

Jetzt soll das Programm als winexe unter Verwendung von WPF geschrieben werden. Die einzelnen Felder der Karte speichern die Anzahl der Besuche der Ameise, nicht die Farbe. Es soll der Maximalwerte der Besuche ermittelt werden, und dann ein 3D-Objekt aus Säulen erstellt werden, dessen Länge den Besuchen entspricht und dessen Farbe der gemäß den Ameisenfeldfarben erzeugt wird.

Als Alternative eine Bitmap, bei der die Pixel weiß sind, oder den Farbwert map[(x,y)] / max_visits für RGB haben (Graustufenbild).

Wie würde man das umsetzen?
BlackJack

@self.made_man: Warum hast Du das denn dermassen stark und unnötig von .NET abhängig gemacht? ``print`` und `raw_input()` gibt es auch in IronPython.

Anmerkung zur Messung: Wenn Du die Leistung von Wörterbüchern messen willst, dann solltest Du die Ausgabe auf die Konsole nicht mit messen. Das kann je nach beteiligter Software („echte” Konsole, Textausgabe in einer IDE, Grafikkartentreiber, …) sehr stark schwanken und ausserdem die eigentliche Rechenzeit der Programmlogik auch gerne mal sehr stark dominieren, so dass man gar nicht das misst, was man eigentlich wollte, sondern hauptsächlich die Leistungsfähigkiet des Grafiksystems, wie schnell oder eben auch wie langsam das Buchstaben malen kann.

Der Name `TPLA_world` ist ziemlich kryptisch und entspricht auch nicht der Namenskonvention. In Python 2 sollte man immer von `object` erben um eine „new style”-Klasse zu erhalten, auf der zum Beispiel dann auch `property()` funktioniert, was sich hier für Attribute wie `width` und `height` anbieten würde. `LMT` ist auch kein guter Name und das hätte ich nicht mit in die `__init__()` gepackt, sondern eine Klassenmethode dafür geschrieben. `rule_deep` sollte `rule_length` heissen, wobei ich das weg lassen würde oder zumindest als `property()` definieren würde, weil man sich sonst eine unnötige Redundanz einbaut.

Für meinen Geschmack sind zu viele ``if``-Kaskaden die man kürzer ausdrücken könnte, zum Beispiel durch Wörterbücher die Regelbuchstaben auf Zahlen abbilden, eine Liste mit relativen Koordinaten die auf die Position addiert werden, und den Einsatz von `min()` und `max()`.

Wenn man eine `range()` (oder in Python 2 besser `xrange()`) rückwärts durchlaufen möchte, ist ``reversed(xrange(a, b))`` IMHO lesbarer und weniger fehleranfällig als ``xrange(b - 1, a - 1, -1)``. An der Stelle solltest Du mal klären ob Dein Code nicht fehlerhaft ist, weil Du von den `max_*`-Werten keine 1 abziehst.

Die Ameise könnte man auch in einen eigenen Typ heraus ziehen.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Hat mich einfach nicht schlafen lassen, dieser ganze .NET-%&$§: ant.py
Antworten