Skip to content
Aug 22 10

SGF Player 2

by admin

Schönheit First

Mich interessiert zuerst, wie ich in SVG ein äußerlich passables Go-Brett mit Steinen hinbekomme. Im Netz finde ich ein mit Inkscape erzeugtes welches. Der SVG-Code ist gruselig: Die Koordinaten für die Gitterpunkte des Brettes haben etliche Nachkommastellen und die Datei enthält viel zu viele Knoten, welche ich nicht verstehe und nicht brauche. Unverständlicher, generierter Code, den ich nicht programmatisch manipulieren möchte, da ich an die Koordinaten und Knoten schlecht heran komme. Also schreibe ich mir meinen SVG-Code anhand von W3C-Spezifikation und Beispielen selber. Als erstes erzeuge ich mir mit dem SVG-Attribut ViewBox ein eigenes Koordinatensystem. Dieses Koordinatensystem lässt sich als Auflösung auffassen. Ich sage damit, wie groß meine Auflösung immer ist, egal, wieviel Platz ich tatsächlich zur Verfügung habe. Das macht mein SVG-Dokument skalierbar.
Für das Holzbrett zeichne ich nun ein Rechteck in hellbraun. Da ich unbedingt eine Art Holzmaserung sehen will und andernfalls nicht auf das Brett gucken kann, definiere ich mir für das Rechteck einen Filter, der aus den Effekten feTurbulence und feDiffuseLighting besteht.
Das Gitter zu zeichen ist trivial, doch das mache ich lieber in Java mit einer for-Schleife, da ich dann alle Abstände zentral ändern kann, statt 19 mal 19 Linien händisch zu programmieren. Um die Steine zu zeichnen definiere ich mir für schwarze und weiße Steine jeweils einen radialGradient-Knoten und einen Filter für den Schatten. Nachdem ich probeweise einige Steine gezeichnet habe, sehen diese zwar ganz reizend aus, aber wenn der Schatten zu groß ist, fällt er über den benachbarten Stein. Ich finde auf Anhieb keine Lösung, bin aber damit zufrieden, wenn der Schatten nur 1 Pixel breit ist und dieser bei einem Abstand eines Steines vom nächsten von ebenfalls 1 Pixel nur noch aufs Brett fällt.
Nun fange ich an, für ein Spiel Stein für Stein aufs Brett zu zeichnen, indem ich die ‘Logik’ ausblende. Die Logik besteht daraus, dass auch Steine vom Brett genommen werden müssen, wenn ein gesetzter Stein eine Gruppe gegnerischer Farbe berührt und dieser die letzte Freiheit nimmt. Ich möchte am liebsten pro Zug alles neu darstellen und alle Steinknoten dem SVG Dokument neu hinzufügen. Doch je mehr Steine ich hinzufüge, desto länger dauert es. Da die Verzögerung deutlich erkennbar ist, brauche ich eine Lösung. Im Netz kursiert der Vorschlag, vom SVG-Dokument ein Fragment abzuspalten und alles nötige zuerst dem Fragment anzuhängen, bevor das Fragment als Ganzes in das Dokument eingefügt wird. So müsse die Darstellung des Dokuments nur einmal gerechnet werden. Leider schafft auch dieser Vorschlag keine wesentliche Abhilfe. Deshalb gebe ich jedem Steinknoten eine eindeutige ID und füge nur einen neuen Knoten hinzu, wenn es ihn noch nicht gibt.

Logik

Da die Darstellung der kleinen Anwendung nun flüssig läuft und schön aussieht, kann ich mich nun der Logik zuwenden. Ein Zug verändert den Zustand des Brettes. Ein Zustand ist vom vorherigen Zustand abhängig. Wie kann ich es erreichen, dass ich wahllos zu irgendeinem Brettzustand eines Spiels springen kann, z.B. Zug n? Ich muss für Zug n den Zustand aus den voran gegangenen n-1 Zuständen errechnen. Meine eigenen Gedanken zum Thema Freiheiten von Steinen berechnen und diese Steine nahtlos vom Brett entfernen, erscheinen mir sehr kompliziert und ich schaue mir den offenen Quellcode von qgo an. Dieser Code ist in C++ geschrieben und ich finde sofort, was ich brauche. In dem qgo-Modell existieren Gruppen von Steinen. Beim Setzen eines Steins wird geprüft, ob er einer Gruppe angehört. Dabei wird Stein für Stein geschaut, ob der neue Stein von der Position her ein nördlicher, südlicher, westlicher oder östlicher Nachbar sein könnte. Gehört er zwei Gruppen an, werden die zwei Gruppen zu einer Gruppe verbunden und der Stein hinzugefügt. Gehört er keiner Gruppe an, wird aus ihm eine neue gebildet. Weiter wird pro Zug der Stand der Freiheiten der Gruppen anhand des Brettzustands berechnet. Hat mindestens ein Stein einer Gruppe einen benachbarten Punkt, der weder von einem weißen noch einem schwarzen Stein besetzt ist, besitzt die Gruppe Freiheit. Hat die Gruppe keine mehr, wird sie gelöscht, nachdem Stein für Stein dieser Gruppe vom Brett genommen wird. Es wird also pro Zug allerhand berechnet, aber das geht immer noch schneller, als ich klicken kann.
Zum Schluß hätte ich gerne noch alle 40.000 Spiele, die ich in einer viele MB großen Datei beherberge, in einer übersichtlichen Tabelle. Dort möchte ich Spiele anhand von Spielernamen, Turnier und Datum filtern können. Dies habe ich mir schon einmal mit F# in .NET programmiert. Aber wie ich festellen muss, sind es in Java mehr Zeilen und geht nicht so leicht von der Hand. Die string.split() Funktion in Java ist nämlich viel zu langsam, da sie einen regulären Ausdruck als Argument erwartet. Zum parsen des Strings, der alle Spiele enthält, muss ich ihn char für char begutachten, mir Positionen von Spieltrennern merken und pro Spiel einen Substring aus dem Gesamtstring extrahieren. Auch das läuft nach einigen Optimierungen flüssig, so dass der Import nur ein paar Sekunden dauert.

So sieht der Player aus:
SGF_Player_Batik_Swing

Sogar mein 4-jähriger Sohn kann auf diese Weise Profi-Partien nachspielen! Züge können in der geschwindigkeitsverstellbaren Animation äußerst gut gefunden werden, statt in einem PDF-Dokument, welches 50 Züge als Zahlen beinhaltet, suchen zu müssen.

Aug 22 10

SGF Player 1

by admin

Ich schaute mir ja so gerne Amatuerpartien von hochrangigen Go-Spielern auf dem KGS-Server an. Doch jetzt schaue ich mir, seit dem ich mir einen SGF Player entwickelt habe, lieber Profi-Partien an. Als ich mit einem Kollegen über mein Vorhaben sprach, war sein erster Rat: SVG. Dieses Format findet demnächst seinen Weg in den Internet Explorer 9 und in den Firefox ist es jetzt schon integriert. Erste Erkundungen waren beeindruckend. SVG öffnet einen Weg, Spiele in den Browser zu bringen, indem SVG-Inhalte, die sich im DOM befinden, über Javasript manipuliert werden können. Doch wie viele Enwtickler machte ich um Javascript bisher einen weiten Bogen. Lange gab es für die Entwicklung mit Javascript keine IDE, was sich jetzt eigentlich geändert haben dürfte. Aber eine neue Technologie, nämlich SVG, ist mir erst einmal genug. Ich will Schritt für Schritt vorgehen und nur eine neue Sache auf einmal lernen. Und schließlich muss es nicht direkt ins Web. Mein Blick fällt auf die Bibliothek Batik, welche die Manipulation von SVG mittels der mir vertrauten Sprache Java ermöglicht. Das Beispiel sieht denn auch sehr vielversprechend aus: Es ist sogar möglich, ein Applet zu entwickeln, welches sehr wohl übers Web ausgeführt werden kann.
Als nächstes entscheide ich mich für die NetBeans IDE und binde die Batik-Archive in mein Projekt ein. NetBeans behagt mir auf Anhieb. Die Bedienung ist sehr ähnlich zum Visual Studio. Im Gegensatz zur C++-Unterstützung von Visual Studio kann ich hier jedoch sehr bequem Klassen oder das Projekt umbennennen, Code refaktorisieren und fehlende Importe einfügen. Da ich jetzt doch wieder einen Haufen neuer Technologien auf einmal vorfinde, will ich zunächst die Batik Beispielanwendung in eine Swing-Anwendung überführen.
Das Ergebnis:

Batik-Beispiel in Java Swing - klein

Batik-Beispiel in Java Swing - klein


Batik-Beispiel in Java Swing - groß

Batik-Beispiel in Java Swing - groß

Das Ergebnis stellt mich sehr zufrieden und entspannt lehne ich mich zurück. Mit Java unter Linux bin ich zufrieden. Swing schnurrt schnell und Anwendung und IDE, die ebenfalls in Java programmiert ist, laufen flüssig. Das SVG-Dokument ist interaktiv über Java manipulierbar und die Manipulation verläuft ohne Flackern. Das Ergebnis ist frei skalierbar, ohne, dass ich hierfür Aufwand betreiben müsste. Die Technologien sind gefunden, um meinen ersten SGF-Player zu verwirklichen.

Jul 19 10

Hin g’frickelt is’

by admin

Ich habe 4 Jahre lang ausschließlich Ubuntu benutzt. Nein, die Steuererklärung habe ich nach wie vor mit Elster auf dem Windows-Notebook meiner Frau gemacht. Auf diese Bequemlichkeit will ich nicht verzichten, einfach die Formulardaten vom letzten Jahr kopieren, ändern und abschicken.
Ich kann wirklich nicht bestätigen, dass Ubuntu grenzenlos stabil ist. Ich erinnere mich da an die Systemabstürze, die durch zerkratzte DVD meiner Kinder verursacht wurden. Jedes Mal musste ich dann die Festplatte mit *Werkzeug vergessen* reparieren. Einmal jedoch schlug das fehl und eine Neuinstallation war fällig. Das Gleiche, wenn mein Sohn den Stecker bei laufendem Betrieb aus der Steckdose zog. Eine Maßname, die mir sehr oft weiter geholfen hat: Die System- von der Datenpartition zu trennen und öfter mal ein Systemimage anzufertigen. Oft zerschießt man sich ja auch das System, weil man mal eben einen Befehl ausprobiert. Von daher kann ich Ubuntu nicht die alleinige Schuld für manche Abstürze geben.
Neuerdings habe ich mal wieder Windows 7 installiert. Ich wollte nämlich der Weiterbildung frönen und mich in .NET einarbeiten. Ich dachte mir, mit C++ bin ich etwas antiquiert. Vor allem, weil ich beruflich sehr gerne agil entwickeln will und man dies wohl viel häufiger in modernen Sprachen tut, während man in C++ nur Anwendungen zu Tode wartet.
Nun, da ich meine Erfahrung mit .NET gesammelt habe, habe ich mir wieder ein Linux gegönnt. Ich habe also Unbuntu 10 installiert und: Mein lieber Herr Gesangsverein! Das funktioniert ja überhaupt nicht. Ich nahm an, so richtig gut läuft es nur mit proprietärem Grafikkartentreiber. Aber nach dessen installation war die Soundausgabe gemutet und ich bleibe beim herunter fahren beim Anmelden-Dialog hängen! Das Ganze ist auf meinem Rechner reproduzierbar. Für eine große Verwirrung sorgte auch, dass nach installieren des Treibers der kleine am VGA angeschlossene Bildschirm angepeilt wird, den ich für gewöhnlich ausschalte. Der große Monitor am DVI bleibt dunkel. Bis ich das gerafft hatte, das kleine Dinge einzuschalten. Also versuche ich es mit Kubuntu. Ich habe auch an OpenSuse gedacht, aber Ubuntu hat mich so lange treu begleitet und schließlich hat jede Distribution ihre Macken. Und Kubuntu ist ja auch ein Schlingel. Ich habe aus Angst zunächst mal den Grafikkartentreiber weg gelassen. Nachdem ich meine 2 Monitore auf Ausrichtung und Auflösung eingestellt habe und neu starte, hat KDE diese Einstellung vergessen! Ich musste diese Einstellungen tatsächlich per Hand in eine Datei eintragen. Mißtrauisch geworden, will ich zunächst meine Installation mit partimage sichern. Doch das unterstützt das neue Dateisystem ext4 nicht. Ich habe dann fsarchiver gefunden, dass den Dienst tat. Dieses Mal hat der Treiber nicht mehr das System zerschossen, Glück gehabt. Eine Schande: Lege ich eine Daten-CD ins Laufwerk, erkennt der KDE-Dateimanager Dolphin dies nicht. Erst nachdem ich Nautilus von Gnome installiere und diesen über die Shell starte kann ich meine CD öffnen.
Damit kann ich leben, eindeutig. Jetzt habe ich auch wieder ein lauffähiges System. Aber diese Frickelei !!! Wahnsinn! Warum mache ich das? An Linux hat mich immer die Tatsache fasziniert, dass es aus freiem Antrieb sozusagen freiwillig entwickelt wurde. Dass es so prächtig funktioniert kann eindeutig kein Grund sein. In dieser Hinsicht bin ich aus rein ideellen Gründen überzeugter Linux-Benutzer. Beruflich hätte ich aber grundsätzlich nichts gegen .NET einzuwenden.

Jul 3 10

ASP.NET MVC – Tutorials im Umbruch

by admin

Wie beginnt man am besten das Erlernen von ASP.NET MVC? Da MVC allermeist für große und wartbare Anwendungen eingesetzt wird, sind Schreiber von Tutorials versucht, ihr ganzes designtechnisches Können einzubringen. Das kann aber leicht den Blick auf das reine MVC verschleiern. Zudem ist ASP.NET MVC 2 gerade aktuell und alle raffiniert entworfenen Tutorials auf einen Schlag veraltet. Kommen noch sehr spezielle Techniken wie z.B der Inversion of Control-Container Castle Windsor hinzu, wird das Tutorial noch kurzlebiger. Aber schließlich bedeutet MVC erst einmal nichts anderes als die Trennung von Model, View und Controller. Die Persistenzschicht ist davon völlig unabhängig. Also lege ich mir für eine Beispielanwendung zuerst eine Grundlage, indem ich ein Datenmodell entwerfe und denke gar nicht daran, dass ich ja ASP.NET MVC lernen will. Mit dem Entity Framework 4 habe ich ja schon gute Erfahrungen gesammelt und entwerfe nachfolgend abgebildetes Modell: Ein Carsharing-Unternehmen hat verschiedene Autos und verschiedene Personen, die am Unternehmensmodell teilnehmen. Eine Person kann mehrere Buchungen machen und mehrere Buchungen können sich auf ein Auto beziehen.

car_sharing_datamodel

Das Letzte Mal, als ich mit dem EF 4 arbeitete, schrieb ich meine POCO-Entitäten und den Datenzugriff selbst. Meine Intuition verrät mir, dass das auch besser gehen kann und ich stoße auf den C# POCO Entity Generator, der von Microsoft bezogen werden kann. Mit dessen Hilfe werden beim Speichern des Datenbankmodells nach einem Template (T4-Template?) sowohl POCO-Entitäten als auch der Datenzugriffskontext generiert. Das Schöne: Ich kann das Template ändern. Will ich weiterhin den Kontext ein Interface implentieren lassen, ist das Template der Ort für diese zusätzlichen Codezeilen. Denn den generierten Code sollte man nicht anfassen, da bei einem lebenden Modell die Codegenerierung immer wieder erneut angestoßen wird.

Nachdem ich nach gewohntem Muster wieder ein Repository mit austauschbarem Kontext angelegt habe, zeigt mir ein Unittest im folgenden Code, dass die Persistenz ignorierenden generierten Entitäten die Konsistenz der Datenbank auch ohne mein Zutun gewährleisten. Die Tests sind nicht umfangreich, denn ich vertraue Microsoft.

        [TestMethod]
        public void TestConsistency()
        {
            Person p1 = _repository.GetPersonByEmail("h@d.de");
            Person p2 = _repository.GetPersonByEmail("o@s.de");

            IEnumerable<Booking> bookings = _repository.GetBookings(p1);

            foreach (Booking b in bookings)
                Assert.IsTrue(p1.Id == b.PersonId);

            Booking b1 = bookings.ElementAt(0);

            Assert.IsTrue(p1.Bookings.Contains(b1));

            b1.Person = p2;
            Assert.IsTrue(!(p1.Bookings.Contains(b1)));
            Assert.IsTrue(p2.Bookings.Contains(b1));
        }

Nun, da ich meine Persistenzschicht sauber abgetrennt habe, kann ich mich auf das eigentliche ASP.NET MVC-Framework stürzen. Ich habe keine Angst mehr vor dem ’schmutzigen’ Tutorial ASP.NET MVC Music Store von Microsoft. Ich genieße, dass es hier mal nicht ums Design geht und nun tut das Tutorial seinen Dienst und ist sehr gut nachvollziehbar. Das Übertragen auf meine eigene skizzierte Beispielanwendung Car Sharing zeigt mir, dass ich es verstanden habe. Interessant finde ich, dass die View-Schicht nichts mit dem Style zu tun hat, den man wiederum in einem separaten Stylesheet definiert. Die nachfolgenden Abbildungen kann ich mir nicht verkneifen.

car_sharing_home
car_sharing_car

Jun 24 10

ADO.NET Entity Framework goes DDD und TDD

by admin

Nachdem die Entwicklergemeinde das Entity Framework mit einer Vote of No Confidence gewürdigt und damit deutlich gemacht hat, dass der Persistensbaukausten in die falsche Richtung läuft, hat Microsoft nachgebessert. Plain old CLR Objects (POCO) werden nun unterstützt, was TDD ermöglicht. Das Datenbankschema allerdings bleibt Grundlage für eine EF-Anwendung. Mich hat es gereizt, eine Beispielanwendung zu schreiben. Ich finde den Gedanken faszinierend, eine Datenbankanwendung modell- und testgetrieben zu entwickeln und gleichzeitig von der Persistenzschicht zu abstrahieren, indem ich das Framework die lästigen Routinearbeiten erledigen lasse.

Nun habe ich denn mit einem Datenbankschema begonnen, welches nachfolgend abgebildet ist. Die Entitäten Person und Booking wurden mit Visual Studio modelliert. Eine Person kann mehrere Buchungen eines Ferienhauses machen. Die beiden Entitäten Person und Booking haben jeweils einen Primärschlüssel, ihre IDs. Booking hat einen Sekundärschlüssel, die ID der Person. Dieses Modell sorgt für Konsistenz. Die Datenbank erlaubt es fortan nicht, dass eine Person, auf die eine Buchung verweist, gelöscht oder deren ID verändert wird. Aus diesem Datenbankschema erzeuge ich SQL-Code, um meine Datenbank mit Tabellen zu füllen.

generate_database

Wie man in der folgenden Abbildung sieht, teilt die Solution Anwendung, Modell, Persistenz und Tests in seperate Projekte auf. Das Modell-Projekt hat keine Referenz auf andere Projekte, sondern wird von allen Projekten benutzt. Das habe ich der POCO-Unterstützung zu verdanken. Mit EF4 ist es möglich, mit Objekten zu arbeiten, die die Persistenz ignorieren.

solution_model

Mit unabhängigen POCO wird die Verantwortung allerdings dem Modell übertragen, dass nun selber für Konsistenz sorgen muss. Die Setter Methode Personder Klasse Booking, muss _personID im folgenden Code auffrischen. War _personID vorher ein anderes Objekt, muss das vorherige Objektthis aus der Liste der Buchungen herausnehmen. _personID muss hingegen this zur Liste der Buchungen hinzufügen.

namespace Model
{
    public partial class Booking : IValidate
    {

        #region Primitive Properties

        public virtual int ID { get; set; }
        public virtual DateTime StartDate { get; set; }
        public virtual DateTime EndDate { get; set; }

        public virtual int PersonID
        {
            get { return _personID; }
            set
            {
                if (value != _personID)
                {
                    if (_person != null && _person.ID != value)
                    {
                        Person = null;
                    }

                    _personID = value;
                }
            }
        }
        private int _personID;

        #endregion

        #region Navigation Properties

        public virtual Person Person
        {
            get
            {
                return _person;
            }
            set
            {
                if (!Object.ReferenceEquals(_person, value))
                {
                    var existingValue = _person;

                    _person = value;

                    if (value != null)
                    {
                        _personID = value.ID;
                    }
                    if ((existingValue != null) && (existingValue.Bookings.Contains(this)))
                    {
                        existingValue.Bookings.Remove(this);
                    }

                    if ((value != null) && !value.Bookings.Contains(this))
                    {
                        value.Bookings.Add(this);
                    }
                }
            }
        }

        private Person _person;

        #endregion

        #region IValidate Members
        void IValidate.Validate(ChangeAction action)
        {
            if (action == ChangeAction.Insert)
            {
                if (this.Person == null)
                    return;

                if (this.Person.Bookings.Count(c => c.StartDate == this.StartDate ) > 1)
                    throw new InvalidOperationException(
                "The booking overlaps with another booking of this person");
            }
        }

        #endregion
    }
}

Andererseits fügt Person im folgenden Code im Konstruktor der Liste von Buchungen einen Delegaten hinzu, der aufgerufen wird, wenn eine Buchung hinzugefügt oder entfernt wird. Einer neu hinzugefügten Buchung wird this zugeordnet, einer entfernten Buchung wird this entzogen.

namespace Model
{
    public partial class Person : IValidate
    {
        public Person()
        {
            _bookings = new ObservableCollection<Booking>();
            ((ObservableCollection<Booking>)_bookings).CollectionChanged += (sender, e) =>
            {
                if (e.NewItems != null)
                    foreach (Booking item in e.NewItems)
                        if (item.Person != this)
                            item.Person = this;

                if (e.OldItems != null)
                    foreach (Booking item in e.OldItems)
                        if (item.Person == this)
                            item.Person = null;
            };

        }

        public virtual int ID { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }

        #region Navigation Properties

        public virtual ICollection<Booking> Bookings
        {
            get
            {
                return _bookings;
            }
        }

        private  ICollection<Booking> _bookings;
        #endregion

        #region IValidate Members

        public void Validate(ChangeAction action)
        {
        }

        #endregion
    }

}

Als nächstes muss der Datenbankzugriff gekapselt werden, um vollständige Testbarkeit zu gewährleisten. Das erledigt man überlichweise mit einem Repository. Da ich testgetrieben entwickeln will, abeitet mein Repository auf einem austauschbaren Context. Für die Tests wird ein Fakekontext benutzt, für den Produktionscode der direkte Datenbankzugriff. Der Context ist eine Implementierung des nachfolgenden Interface IEntities:

namespace Model
{
    public interface IEntities : IDisposable
    {
        IObjectSet<Person> Persons { get; }
        IObjectSet<Booking> Bookings { get; }
        int SaveChanges();

    }
}

Hier die Klasse Repository:

namespace Model
{
    public class Repository
    {
        private IEntities _context;

        public Repository(IEntities context)
        {
            if (context == null)
                throw new ArgumentNullException("context was null");
            _context = context;
        }

        public void AddBooking(Booking b)
        {
            _context.Bookings.AddObject(b);
        }

        public void AddPerson(Person p)
        {
            _context.Persons.AddObject(p);
        }

        public Person GetPersonByID(int id)
        {
            return _context.Persons.First(p => p.ID == id);
        }

        public void SaveChanges()
        {
            _context.SaveChanges();
        }
    }
}

Wie der Kontext fake- und datenbankseitig implementiert wird, verschweige ich hier mal. Die Schablone dafür habe ich mir von diesem Blog abgeschaut.

Ein Beispieltest beweist mir, dass eine Exception geschmissen wird, wenn eine Person 2 Buchungen macht, deren Startdatum gleich sind:

namespace Tests
{
    [TestClass]
    public class CommentTests
    {
        private IEntities _context;

        [TestInitialize]
        public void TestSetup()
        {
            Person p = new Person()
            {
                FirstName = "Jonathan",
                LastName = "Aneja",
                ID = 1
            };

            Booking b = new Booking()
            {
                StartDate = new DateTime(2010, 10, 1),
                EndDate = new DateTime(2010, 10, 8),
                Person = p
            };

            _context = new FakeEntities();
            var repository = new Repository(_context);

            repository.AddPerson(p);
            repository.SaveChanges();
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void AttemptedOverlappingBookings()
        {
            var repository = new Repository(_context);
            Booking b2 = new Booking()
            {
                StartDate = new DateTime(2010, 10, 1),
                EndDate = new DateTime(2010, 10, 8),
                Person = repository.GetPersonByID(1)
            };

            repository.AddBooking(b2);
            repository.SaveChanges();

        }
    }
}

Hier die Frucht des Ganzen: ein wunderschöner objektorientierter Code, um einen Benutzer und dessen Buchung in der Datenbank anzulegen:

namespace Application
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person()
            {
                FirstName = "aaaaa",
                LastName = "bbbb"
            };

            Booking b = new Booking()
            {
                StartDate = new DateTime(2013, 10, 1),
                EndDate = new DateTime(2013, 10, 8),
                Person = p1
            };

            var _context = new VacationHomeBookingEntities();
            var repository = new Repository(_context);

            repository.AddBooking(b);

            try
            {
                repository.SaveChanges();
            }
            catch (InvalidOperationException e)
            {
                Console.WriteLine("{0} Exception caught.", e);
                Console.ReadKey();

                return;
            }

        }
    }
}
Jun 5 10

Gospiel-Datenbank frei nach MVVM

by admin

Nicht, dass das Model View View Model (MVVM) der Weisheit letzter Schluß wäre, um wartbare Anwendungen in WPF zu schreiben. Es ist lediglich ein Muster, dass sich in letzter Zeit eingebürgert hat und sich ganz gut anhört: Es gibt ein getrenntes Modell, dass es bei MVC auch stets gibt. Das hat den Vorteil, dass man die Intelligenz der Anwendung testgetrieben entwickeln kann. Aber auch Feinde der testgetriebenen Anwendung sehen in solch einer Trennung Vorteile, bleibt doch der Modellcode rein und lesbar und die Anwendung kann nach einzelnen Aspekten entwickelt werden. Schließlich kann reiner Modellcode den Lauf der Zeiten besser überdauern, da man ihn direkter und einfacher ändern kann, statt irrelevante oder oberflächliche Dinge stets mit in die Betrachtung ziehen zu müssen. Das View-Model ist ein weiteres Modell, allerdings dient es nur der View und beschreibt, nach welchem Modell diese funktioniert. Es gehört also nur zu dieser Sicht auf die Daten.

Um das MVVM-Muster auszuprobieren, programmiere ich mir wieder einen lange gehegten Wunsch. Ich habe 40.000 Go-Spiele als einzelne Datei und als ebenso viele Dateien vorliegen und würde mir gerne eine komfortable Suche gönnen, statt in einer 57 MByte großen Datei mit einem Texteditor nach Spielen zu fischen.

Das Modell ist extrem simpel und für dieses Modell lohnt sich der Aufwand natürlich nicht. Sei es drum:

namespace mvvmfilter
{
    class GoGame
    {
        public string Player1 { get; set; }
        public string Player2 { get; set; }
        public string Date { get; set;  }
        public string Tournament { get; set; }
        public string Result { get; set; }
        public string Content { get; set; }
    }
}

Interessanter ist das View-Modell. Es ist der umfangreichste Code in der Projektmappe und das zeigt schon, dass meine Anwendung hauptsächlich aus einer Oberfläche besteht. Das ist auch genau meine Absicht: eine komfortablere Suche. Der Code spiegelt also genau meine Absicht wieder, prima!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using mvvmfilter;
using System.Collections.ObjectModel;
using Microsoft.Practices.Composite.Wpf.Commands;
using System.Windows.Forms;
using System.IO;

namespace mvvmfilter
{
    class ViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<GoGame> GoGameList { get; set; }

        public ObservableCollection<GoGame> FilteredList
        {
            get
            {
                return GetFilteredList(GoGameList);
            }
        }

        public ViewModel()
        {
            GoGameList = new ObservableCollection<GoGame>();

            SaveEquipmentCommand = new DelegateCommand<object>(SaveEquipment);

            foreach (fscharf.GoGame game in fscharf.Games)
                GoGameList.Add(new GoGame() { Player1 = game.PlayerWhite, Player2 = game.PlayerBlack, Date = game.Date, Tournament = game.Tournament , Result = game.Result, Content = game.Content});

        }

        public DelegateCommand<object> SaveEquipmentCommand { get; set; }

        private void SaveEquipment(object pObject)
        {
            Stream myStream;
            SaveFileDialog saveFileDialog1 = new SaveFileDialog();

            saveFileDialog1.Filter = "sgf files (*.sgf)|*.sgf|All files (*.*)|*.*";
            saveFileDialog1.RestoreDirectory = true;

            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                if ((myStream = saveFileDialog1.OpenFile()) != null)
                {

                    string content = ((GoGame)pObject).Content;

                    System.IO.StreamWriter file = new System.IO.StreamWriter(myStream);
                    file.WriteLine(content);

                    file.Close();

                }
            }
        }

        private ObservableCollection<GoGame> GetFilteredList(ObservableCollection<GoGame> pOrginalGoGameList)
        {
            ObservableCollection<GoGame> filteredGoGamelist = new ObservableCollection<GoGame>();

            var x = from p in pOrginalGoGameList
                    where
                        FilterPlayer1Method(p, this.FilterPlayer1) &&
                        FilterPlayer2Method(p, this.FilterPlayer2) &&
                        FilterDateMethod(p, this.FilterDate) &&
                        FilterTournamentMethod(p, this.FilterTournament)
                    select p;

            foreach (var u in x)
                filteredGoGamelist.Add(u);

            return filteredGoGamelist;
        }
        public static bool FilterPlayer1Method(GoGame goGame, string compare)
        {
            if (goGame == null) return false;
            if (compare == null) return false;
            if (compare.Equals(string.Empty)) return true;
            return (goGame.Player1.ToUpper().StartsWith(compare.ToUpper()));
        }

        public static bool FilterPlayer2Method(GoGame goGame, string compare)
        {
            if (goGame == null) return false;
            if (compare == null) return false;
            if (compare.Equals(string.Empty)) return true;
            return (goGame.Player2.ToUpper().StartsWith(compare.ToUpper()));
        }

        public static bool FilterDateMethod(GoGame goGame, string compare)
        {
            if (goGame == null) return false;
            if (compare.Equals(string.Empty)) return true;

            return (goGame.Date.ToUpper().StartsWith(compare.ToUpper()));
        }

        public static bool FilterTournamentMethod(GoGame goGame, string compare)
        {
            if (goGame == null) return false;

            if (compare.Equals(string.Empty)) return true;
            return (goGame.Tournament.ToUpper().StartsWith(compare.ToUpper()));
        }

        private string _FilterPlayer2 = string.Empty;
        public string FilterPlayer2
        {
            get
            {
                return _FilterPlayer2;
            }
            set
            {
                _FilterPlayer2 = value;
                NotifyPropertyChanged("FilterPlayer2");
                NotifyPropertyChanged("FilteredList");
            }
        }

        private string _FilterPlayer1 = string.Empty;
        public string FilterPlayer1
        {
            get
            {
                return _FilterPlayer1;
            }
            set
            {
                _FilterPlayer1 = value;
                NotifyPropertyChanged("FilterPlayer1");
                NotifyPropertyChanged("FilteredList");
            }
        }

        private string _FilterDate = string.Empty;
        public string FilterDate
        {
            get
            {
                return _FilterDate;
            }
            set
            {
                _FilterDate = value;
                NotifyPropertyChanged("FilterDate");
                NotifyPropertyChanged("FilteredList");
            }
        }

        private string _FilterTournament = string.Empty;
        public string FilterTournament
        {
            get
            {
                return _FilterTournament;
            }
            set
            {
                _FilterTournament = value;
                NotifyPropertyChanged("FilterTournament");
                NotifyPropertyChanged("FilteredList");
            }
        }

        #region INotifyPropertyChanged Member

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}

Das Viewmodel implementiert das Interface INotifyPropertyChanged und besteht im wesentlichen daraus, Spiele aus einer Originalcollection bei Veränderung der Suchparameter in eine ObservableCollection zu pumpen, welche mit der View über Data Binding verknüft ist. Außerdem reagiert das View-Model auf Benutzereingaben in die Controls des Filters.

Den Code, um die Orginalcollection zu befüllen, habe ich aus Spaß an der Freude mit F# geschrieben:

module mvvmfilter.fscharf

#light

open System.IO

type GoGame = class
      val PlayerWhite: string
      val PlayerBlack: string
      val Date  : string
      val Tournament : string
      val Content : string
      val Result : string
      new (playerWhite, playerBlack, date, tournament, result, content) =
        { PlayerWhite = playerWhite ; PlayerBlack = playerBlack; Date = date; Tournament = tournament; Result = result; Content = content;}
end

let Games = new System.Collections.Generic.List<GoGame>()

let files = Directory.GetFiles(@"C:\Users\Oliver\Downloads\FreeProGames\AllSeperated")

let extract (ls: string, ps: string) =
    let indexpw1 = ls.IndexOf(ps)
    if indexpw1 < 0 then
        ""
    else
        let indexpw1x = indexpw1 + 3
        let indexpw2 = ls.IndexOf("]", indexpw1x)
        if indexpw2 > 0 then
            ls.Substring(indexpw1x, indexpw2-indexpw1x)
        else
            ""

let newgame (gamestr: string) =
    Games.Add( new GoGame(extract(gamestr,"PW["), extract(gamestr,"PB["), extract(gamestr,"DT["), extract(gamestr,"EV["), extract (gamestr, "RE["), gamestr ))

using (File.OpenText(@"C:\Users\Oliver\Downloads\FreeProGames\collection\Collection.sgf"))
    (fun f->
        while not f.EndOfStream do
            let content = f.ReadToEnd()

            let gamestrings = content.Split[|'('|]
            for i = 0 to gamestrings.Length - 1 do
                let gamestring = "(" + gamestrings.[i]
                newgame(gamestring)

    )

Ohne, das Buch Foundations of F# von Robert Pickering, das mir freundlicherweise mein Arbeitgeber auslieh, wäre ich gar nicht weitergekommen. Online-Quellen, selbst die von Microsoft sind sehr unergiebig. Dafür gab das Buch alles, was ich brauchte, her.
Im View-Model kopiere die in F# bestückte Collection in eine C#-Collection, die dann der Anwendung als Datengrundlage dient. Ich habe festgestellt, dass der Datenimport mit F# unvergleichlich viel schneller geht, wenn ich alle Spiele aus der 57 MB großen Datei hole (einige Sekunden), statt aus 40.000 einzelnen Dateien. Die Festplatte muss da ein sehr kleines, Nadelöhr darstellen. So dauert es eine gefühlte halbe Stunde:

for filepath in files do
    using (File.OpenText(filepath))
        (fun f ->
            while not f.EndOfStream do
                let line = f.ReadToEnd()

                let game = new GoGame(extract(line,"PW["), extract(line,"PB["), extract(line,"DT["), extract(line,"EV["), extract (line, "RE["), line )
                Games.Add(game)

        )

So sieht sie dann aus, die Oberfläche:
mvvm_gui

Es gibt nicht so viele Gospieler, die außerhalb ihrer Karriere eine Bedeutung für die Nachwelt erlangt haben. Zwei Persönlichkeiten, die aber für ihren unkomplizierten Stil bekannt sind, sind Takagawa Kaku und Otake Hideo. Also lade ich mir mal ein Spiel von diesen zwei von einem NHK-Turnier herunter. Diese Turniere haben einen sehr knappen Zeitrahmen, so dass schon gar keine Zeit existiert, um sich komplizierte Varianten auszudenken. Schnelle Spiele könnten also durch gute Form beeindrucken. Das Nachlegen der ersten Partie hat einigen Spaß bereitet:
spaß_am_nachlegen

Mai 9 10

XML Datei mit extrem wenig Codezeilen via Data Binding bearbeiten

by admin

Wie kann ich in .NET mit möchst wenigen Codezeilen und möglichst viel XAML eine XML-Datei bearbeiten? Es ist tatsächlich mit sehr wenigen Zeilen möglich. Man sieht nur vor lauter Codebeispielen die Lösung nicht. Im Prinzip sagt die MSDN-Dokumentation schon viel: Mit XMLDataProvider können die einer Oberfläche zugrunde liegenden Daten via Data Binding irgendein XML-Knoten sein.

Hier die XML-Datei, die meiner Beispieloberfläche als Datengrundlage dient:

<?xml version="1.0" encoding="utf-8"?>
<Books xmlns="">
  <Book ISBN="0-7356-0562-9" Stock="in">
    <Title>XML in Action</Title>
    <Summary>XML Web Technology</Summary>
    <Cover>http://www.microsoft.com/library/images/worldwide/mspress/2461.gif</Cover>
  </Book>
  <Book ISBN="0-7356-1377-X" Stock="in">
    <Title>Introducing Microsoft .NET</Title>
    <Summary>Overview of .NET Technology</Summary>
    <Cover>http://www.microsoft.com/library/images/worldwide/mspress/5201.gif</Cover>
  </Book>
  <Book ISBN="0-7356-1288-9" Stock="out">
    <Title>Inside C#</Title>
    <Summary>C# Language Programming</Summary>
    <Cover>http://www.microsoft.com/MSPress/books/imgt/5027.gif</Cover>
  </Book>
  <!--lsdfkgsdlkfgölsdfgls-->
  <Book ISBN="0-7356-1370-2" Stock="in">
    <Title>Programming Microsoft Windows With C#</Title>
    <Summary>C# Programming using the .NET Framework</Summary>
    <Cover>http://www.microsoft.com/MSPress/books/imgt/5188.gif</Cover>
  </Book>
  <Book ISBN="0-7356-1448-2" Stock="out">
    <Title>Microsoft C# Language Specifications</Title>
    <Summary>The C# language definition</Summary>
    <Cover>http://www.microsoft.com/MSPress/books/imgt/5490.gif</Cover>
  </Book>
  <Table>
  </Table>
</Books>

Mit XPath navigiere ich in der Datengrundlade und weise meinen Controls ihren XML-Knoten zu. Hier der XAML-Code:


<Window x:Class="XMLEdit.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="350" Width="525">

        <Window.Resources>
        <XmlDataProvider x:Key="BookData" Source="C:\Users\Oliver\Documents\Visual Studio 2010\Projects\XMLEdit\XMLEdit\books.xml" XPath="Books" />
        <DataTemplate x:Key="BookDataTemplate">
            <Border SnapsToDevicePixels="True" Padding="10" Margin="5" CornerRadius="5" BorderThickness="2" BorderBrush="SteelBlue" >
                <StackPanel>
                    <TextBlock Text="{Binding XPath=Title}" FontSize="14" FontWeight="Bold" Margin="0,0,0,5"></TextBlock>
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Image Source="{Binding XPath=Cover}" Width="100"></Image>
                        <StackPanel Orientation="Vertical">
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="ISBN: " FontWeight="Bold"></TextBlock>
                                <TextBox Text="{Binding XPath=@ISBN}" Foreground="OrangeRed"></TextBox>
                            </StackPanel>
                            <TextBox Text="{Binding XPath=Summary}" Margin="0,5,0,0"></TextBox>
                        </StackPanel>
                    </StackPanel>
                </StackPanel>
            </Border>
        </DataTemplate>
        <Style x:Key="StretchedContainerStyle" TargetType="{x:Type ListBoxItem}">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>

    </Window.Resources>

    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">

        <StackPanel>
            <TextBlock Text="Edit XML File using Data Binding via XPath and XMLDataProvider" Padding="5" Height="27" VerticalAlignment="Bottom" ></TextBlock>
            <TextBlock Text="This sample uses extremely few code lines" Padding="5" Height="27" VerticalAlignment="Bottom" ></TextBlock>
            <StackPanel>
                <Button Content="Save"
                Click="Button_Save_Click"
                Margin="0 0 0 10" Height="27" Width="56" />
            </StackPanel>
            <Grid x:Name="Grid1">
                <ListBox
                    ItemsSource="{Binding Source={StaticResource BookData}, XPath=Book}"
                    ItemTemplate="{StaticResource BookDataTemplate}"
                    ItemContainerStyle="{StaticResource StretchedContainerStyle}" >
                </ListBox>
            </Grid>
        </StackPanel>
    </ScrollViewer>
</Window>

Ein paar wenige Codezeilen waren dann doch nötig, um das implizit geladene XMLDocument des XMLDataProvider zu speichern:

using System.Windows;
using System.Windows.Data;

namespace XMLEdit
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Save_Click(object sender, RoutedEventArgs e)
        {

            XmlDataProvider myXmlDataProvider = (XmlDataProvider)Grid1.FindResource("BookData");
            myXmlDataProvider.Document.Save("C:\\Users\\Oliver\\Documents\\Visual Studio 2010\\Projects\\XMLEdit\\XMLEdit\\books.xml");
        }
    }
}
Mai 4 10

INTP

by admin

In jeden guten Traumjob-Ratgeber heißt es, man solle doch erst mal herausfinden, wer man ist und was einem am leichtesten fällt. Schließlich kann man sich einmal verstellen, aber nicht sein ganzes Berufsleben lang. Und wer weiß, was er will, der kann es wohl unterbewußt auch gut erreichen und ist gut darin. Jetzt habe ich einen Persönlichkeitstest gefunden, bei dem ich mich gut aufgehoben fühle: Der MBTI (Meyers Briggs Type Indicator). Bei dieser Typeneinteilung gibt es 16 Typen. Überraschend an diesem Test finde ich, dass er einem wenig Spielraum für restliche Unsicherheiten läßt. Wer nicht extrovertiert ist, was ist der wohl? Introvertiert natürlich. Und überraschend auch, wie nüchtern das dargestellt wird.
Introvertiert bedeutet nicht etwa ’schüchtern’, sondern einfach nur, dass man seine Energie aus seinem eigenen Inneren bezieht, statt aus seiner Außenwelt. Der Introvertierte kann sich prima konzentrieren, weil er ja gar keine Ablenkung sucht. Andererseits sucht er auch die Stille, um sich zu konzentrieren. Bevor er redet, denkt er nach. Er sucht zuerst in sich selbst nach der Lösung. Dadurch eignet er sich sehr dafür bzw. ist bestrebt, selbständig zu arbeiten.
Als nächstes unterscheidet sich der mit den fünf Sinnen von dem mit dem sechsten Sinn. Während ersterer in der Realität voll aufgeht und sich mit den Gegebenheiten abfindet, lebt letzterer nicht in der Gegenwart, sondern hat Verbesserungsmöglichkeiten im Kopf und lebt im ‘Könnte’. Der Intuitive probiert verschiedene Arbeitsstile aus und läßt seine Erfahrung hinter sich. Er liebt die Herausforderung am Lernen neuer Fähigkeiten und folgt seiner Inspiration statt Fakten. Er interessiert sich nicht für Details, sondern für generelle Prinzipien. Ich habe eine interessante Diskussion gelesen, wie denn so ein Intuitiver als Programmierer arbeiten könnte, wo es doch bei der Programmierung extrem ins Detail geht. Schließlich sind unter Programmierern und Softwareentwicklern auffällig viele INTP vorhanden, während der Typ INTP in der Allgemeinbevölkerung sehr selten ist. Nun, beim Programmieren geht es immer noch ums abstrahieren von den wirklich langweiligen Details, die man eben den Computer machen lassen möchte. Und es ist allgemeiner Konsens, dass man jedes Detail nur einmal schreiben und ansonsten wieder verwenden sollte.
Als Drittes wird der fühlende vom denkenden Menschen unterschieden. Hier geht es nicht um Emotionen, unter denen der eine wie der andere Typ leiden können. Gefühl meint, dass man mit anderen mitfühlt, die Wahrheit um des Taktgefühls willen verschweigt und sich für Harmonie interessiert. Der Denker ist ein aufgabenorientierter Mensch. Er will seine Arbeit erledigen und stört sich wenig an Gefühlen. Bereitwillig äußert er Kritik, um sein Ziel besser erreichen zu können.
Schließlich gibt es den Planenden vs. Flexiblen. Der Planende arbeitet am Besten, wenn alles nach Plan verläuft, während der Flexible bei Bedarf handelt. Er schiebt Entscheidungen auf, um mehr Möglichkeiten zu haben. Er widersteht Strukturen und favorisiert sich ändernde Umstände. Der Planende hingegen liebt Termine und erledigt lieber seine Aufgabe pünktlich. Dabei übersieht er neue Möglichkeiten, die am Wegrand stehen.
Nur beim letzten Punkt bin ich mir nicht sicher, ob ich eher ein Planender oder ein Flexibler bin. Eigentlich bin ich extrem aufgabenorientiert, aber wenn es um meinen Urlaub geht, störe ich mich an jedem Plan und möchte unbedingt in den Tag hinein leben.

Angenommen ich bin nun INTP, kann ich mich in die Schublade von Albert Einstein stecken.

Mai 3 10

Erbsenzähler

by admin

Zur Übung, um C# und .NET zu lernen, schreibe ich mir einen Erbsen… äh Zeilenzähler. In XAML schreibe ich mir mal eben eine Oberfläche mit einem Button ‘Load File’, auf dessen Betätigung hin eine Visual Studio C++-Projektdatei auf ihre Filter (Ordner) hin untersucht wird und eine Auswahl an diesen Filtern angeboten wird. Mit dem Button ‘Count’ werden dann alle Zeilen der in diesen virtuellen Ordnern befindlichen Dateien gezählt.

<Window x:Class="TestFileDialog2343.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="350">

    <StackPanel Margin="10" HorizontalAlignment="Left">
        <StackPanel Name="ChooseFilters" HorizontalAlignment="Left" Height="28" Width="91">
            <Button Content="Load File"
                Click="Button_LoadFile_Click"
                Margin="0 0 0 10"/>
        </StackPanel>
        <ListBox ItemsSource="{Binding FilterSelections}" IsSynchronizedWithCurrentItem="True"
        Height="87" HorizontalAlignment="Left" Margin="12,69,0,0" Name="listBox1" VerticalAlignment="Top" Width="276" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding Check}" />
                        <TextBlock Text="{Binding Name}" />

                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <StackPanel>
            <Button Content="Count"
                Click="Button_Count_Click"
                Margin="0 0 0 10"/>
        </StackPanel>
    </StackPanel>
</Window>

Was ich hier außer handwerklichen Dingen wie mit XML-Dokumenten umgehen oder das eigentliche Zeilen zählen gelernt habe, ist die Datenbindung. Hier der Quellcode:

using System;
using System.Windows;
using Microsoft.Win32;
using System.IO;
using System.Xml;
using System.Collections.ObjectModel;

namespace TestFileDialog2343
{
    public class FilterSelection
    {
        public string Name { get; set; }
        public bool Check { get; set; }
    }

    public partial class Window1 : Window
    {
        public ObservableCollection<FilterSelection> FilterSelections { get; internal set; }
        public XmlNodeList FiltersNode;

        public Window1()
        {
            FilterSelections = new ObservableCollection<FilterSelection>();
            InitializeComponent();

            this.DataContext = this;
        }

        private void Button_LoadFile_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.DefaultExt = ".vcproj";
            dlg.InitialDirectory = @"C:\Quellen\nova\Sources\XElektro";
            dlg.Filter = "Projektdateien (.vcproj)|*.vcproj";

            bool? result = dlg.ShowDialog();

            if (result == true)
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(dlg.FileName);

                XmlElement oNode = doc.DocumentElement;

                //XmlNodeList oNodeList = oNode.SelectNodes("/VisualStudioProject/Files/Filter");
                FiltersNode = oNode.SelectNodes("//Filter");

                foreach (XmlNode i in FiltersNode)
                {
                    FilterSelection fs = new FilterSelection();
                    FilterSelections.Add(fs);
                    fs.Name = i.Attributes["Name"].Value;
                }

            }
        }

        private XmlNode GetNode(string name)
        {
            foreach (XmlNode i in FiltersNode)
                if (name == i.Attributes["Name"].Value)
                    return i;

            return null;
        }

        public void Button_Count_Click(object sender, RoutedEventArgs e)
        {
            int count = 0;
            foreach (FilterSelection fs in FilterSelections)
            {
                if (!fs.Check)
                    continue;

                XmlNode i = GetNode(fs.Name);

                System.Diagnostics.Debug.WriteLine(fs.Name);

                if (i == null)
                    continue;

                int r = CountLines(i);

                System.Diagnostics.Debug.WriteLine(r);
                count += r;
            }
        }

        private string GetFilename(XmlNode node)
        {
            Uri uri = new Uri(node.BaseURI);
            string dir = uri.AbsolutePath;
            int lastslash = dir.LastIndexOf("/");
            dir = dir.Remove(lastslash + 1);

            string file = node.Attributes["RelativePath"].Value;
            lastslash = file.LastIndexOf("\\");
            file = file.Substring(lastslash + 1);

            return dir + file;
        }

        private int CountLines(XmlNode filenode)
        {
            int count = 0;

            XmlNodeList list = filenode.SelectNodes("File");
            foreach (XmlNode fnode in list)
            {
                string filename = GetFilename(fnode);

                if (!File.Exists(filename))
                    continue;

                System.Diagnostics.Debug.WriteLine(filename);
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    using (StreamReader sr = new StreamReader(fs))
                    {
                        while (!sr.EndOfStream)
                        {
                            string line = sr.ReadLine();
                            if (line.Length > 0)
                                count++;
                        }
                    }
                }
            }
            return count;
        }
    }
}

Das schöne an WPF ist die mögliche Trennung zwischen Daten und Ansicht. Ich binde meine Listbox an eine ObservableCollection von Objekten der Klasse FilterSelection und den Inhalt der Klasse binde ich an eine benamte Checkbox. So kann ich für jeden Filter entscheiden, ob er mitgezählt werden soll. Unkomplizierter Weise fülle ich eben nicht den Dialog, sondern die Collection mit Daten und die Oberfläche ändert sich wie von Geisterhand. Anders herum ändern sich auch die Daten automatisch mit der Änderung des Benutzers der Obefläche. So brauche ich auch nur die Daten auszulesen und herauszufinden, welche Filter der Benutzer gezählt bekommen möchte.

Apr 28 10

Go-Probleme nach Anzahl der Züge sortieren

by admin

Was ich schon lange vorhatte, aber immer zu faul dazu war: Ich möchte meine Tesuji-Probleme (Tesuji – Taktiken des Nahkampfs im Go-Spiel), die ich mühsam per Hand aus James Davies Buch in einen SGF-Editor eingetippt habe, nach Schwierigkeit sortieren. Sie sind mir nämlich immer noch zu schwer. Zumindest einige davon. Nun, da ich .NET und C# lernen will, kommt dazu die passende Gelegenheit.

Also importiere ich mir alle 434 einzelne SGF (Smart Game Format)- Dateien in ebenso viele Objekte vom Typ GoProblem:

namespace GoProblemsSort
{
    public class GoProblem
    {
        public GoProblem(string filename, string content)
        {
            Topic = GetDescription(filename);
            Content = content;

            int count = Content.Split(';').Length - 1;
            Number = (count-1);

            int descpos = Content.IndexOf("C[");
            if (descpos > 0)
            {
                Content = Content.Insert(descpos + 2, Topic + " ");
            }
            else
            {
                int last = Content.IndexOf(")");
                Content = Content.Insert(last, "C[" + Topic + "]");

            }

        }
        public int Number { get; set; }
        public string Content { get; set; }

        private string GetDescription(string filename)
        {
            int index = filename.IndexOf("_");
            string chapter = filename.Substring(0, index);

            switch (chapter)
            {
                case "2":
                    return "Capture the Cutting Stones";

                case "3":
                    return "Amputate the Cutting Stones";

                case "4":
                    return "Ko";

                case "5":
                    return "When Liberties Count";

                case "6":
                    return "Linking Groups together";

                case "7":
                    return "Into Enemy Territory";

                case "8":
                    return "Escape";

                case "9":
                    return "Sacrifice to gain Tempo";

                case "a9":
                    return "Tesuji for Attack";

                case "b9":
                    return "How to connect";

                case "c9":
                    return "Making Shape";

                case "d9":
                    return "Ignore the Atari";

                case "e9":
                    return "Double-Threat Tesuji";

                default:
                    return "Default Description";
            }
        }

    }

}

Nun schreibe ich mir ein Programm, dass auf Knopfdruck alle Objekte mit dem Dateinamen und dem Dateiinhalt initialisiert. Da ich den Namen der Datei früher so gewählt habe, dass er der Schlüssel für ein Kapitel ist, habe ich jetzt die Gelegenheit mittels String-Manipulation den Kapitel-Text in den SGF-Code Content des Objekts hinein zu schreiben. Die Anzahl der Züge der Lösung interpretiere ich einfach mal als Schwierigkeit und schreibe sie in das Feld Nummer des Objekts. Mit nur einer Zeile sortiere ich alle Objekte, die ich in einer Liste festhalte nach Nummer. Nun lasse ich einen Verzeichnisauswahl-Dialog aufploppen und schreibe alle SGF-Inhalte der Objekte in der von mir geschaffenen Reihenfolge in eine große Datei. Diese Datei kann ich uligo, ein Go-Problemtrainer zu fressen geben


namespace GoProblemsSort
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            GoProblems = new List<GoProblem>();
        }

        public List<GoProblem> GoProblems { get; set; }

        private void Button_LoadFile_Click(object sender, RoutedEventArgs e)
        {
            DirectoryInfo dir = new DirectoryInfo(@"C:\Users\Oliver\Backup\go\tesuji\tesuji");
            foreach (FileInfo f in dir.GetFiles("*.sgf"))
            {

                using (FileStream fs = f.OpenRead())
                {

                    string filecontent = "";

                    byte[] b = new byte[1024];
                    UTF8Encoding temp = new UTF8Encoding(true);
                    while (fs.Read(b, 0, b.Length) > 0)
                    {
                        filecontent += temp.GetString(b);
                    }

                    GoProblem gp = new GoProblem(f.Name, filecontent);
                    GoProblems.Add(gp);

                }

            }

            GoProblems = GoProblems.OrderBy(i => i.Number).ToList();

            FolderBrowserDialog folderBrowserDialog1 = new System.Windows.Forms.FolderBrowserDialog();

            DialogResult result = folderBrowserDialog1.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                string folderName = folderBrowserDialog1.SelectedPath;
                string filename = "Tesujis.sgf";
                string path = @folderName + "\\" + filename;
                FileInfo fi = new FileInfo(path);

                FileStream fs = fi.Create();
                StreamWriter sw = new StreamWriter(fs);

                foreach (GoProblem gp in GoProblems)
                    sw.Write(gp.Content);
            }

        }
    }

}