PhiLIOsoph

Seite:  1 2 Nächste Seite
23.05.2007

PHP: Smarty und die Unterstützung mehrerer Sprachen / Multilingualität

Abgelegt unter: PHP, Tutorials — Robert @ 23:27:48

Smarty ist die Templateengine für PHP schlechthin. Smarty ist sehr schnell und kann einfach erweitert werden. In diesem Artikel geht es darum, wie man Smarty mehrsprachig betreiben kann bzw. die Templates sprachunabhängig umsetzen kann. Am Ende des Artikels kann der Code heruntergeladen werden.

(Hinweis: In diesem Artikel wird PHP5 verwendet und vorrausgesetzt!)

Mit Smarty und ein paar Tricks ist es im Grunde sehr einfach, ein Projekt mehrsprachig zu gestalten. Dazu liest man alle nötigen Strings aus Sprachdateien aus und fügt sie an den jeweiligen Stellen ein. Freiwillige Übersetzer erstellen dann unterschiedliche Sprachdateien und man kann das Projekt auch in anderen Sprachen veröffentlichen.

Bei Smarty bedient man sich für diesen Zweck eines Prefilters. Ein solcher Prefilter verändert die Templates noch bevor sie von Smarty “kompiliert” werden.

So ist es z.B. möglich, ein Template wie folgt zu gestalten:

<table border="0">
		<tr>
			<td>##user_form_username##</td>
			<td><input type="text" name="user" value="" /></td>
		</tr>
		<tr>
			<td>##user_form_password##</td>
			<td><input type="password" name="pass" value="" /></td>
		</tr>
</table>

Die von Rauten (#) umschlossenen Zeichenketten spezifizieren einen bestimmten Sprach-String, dessen Ersetzung in den jeweiligen Sprachdateien angegeben wird. Eine deutsche Sprachdatei könnte etwa so aussehen:

user_form_username=Benutzername
user_form_password=Password

Der Prefilter sucht also nach Sprachstrings und fügt an ihrer Stelle die entsprechende Ersetzung ein – je nachdem, welche Sprache eingestellt ist (z.B. in einer serverseitigen Konfigurationsdatei oder innerhalb der übermittelten Browserdaten des Clients usw.).

Für diese Funktionalität empfehle ich eine Klasse, die das Auslesen der Sprachdateien übernimmt. Der Einfachheit nenne ich sie “Lang“.

Ich muss aber darauf hinweisen, dass ich diese Klassen aus einem meiner eigenen Projekte entnommen habe. Daher kann es sein, dass ich teilweise vergessen habe, den Code an diesen Artikel anzupassen. Wenn also irgendetwas von “OBST” oder “XDB” dasteht, wisst ihr Bescheid. ;)

Die Klasse Lang:

<?php
    /**
      * @author: Robert Nitsch
      */
    class Lang {
 
        public $lng;
        protected $lng_strings;
 
        /**
          * Lädt alle Sprachstrings aus der entsprechenden Sprachdatei.
          * @param string $lng Die Sprache, die geladen werden soll, als Länderkürzel.
          */
        function load_lng($lng)
        {
            $this->lng = $lng;
 
            /* DIR_ROOT muss außerhalb dieser Datei deklariert werden und muss den Pfad
                 zum Hauptverzeichnis des Webprojekts beinhalten
                 Außerdem setzt diese Klasse eine gewisse Verzeichnisstruktur vorraus... (/include/lng) */
            $path = DIR_ROOT.'/include/lng/'.$lng.'.txt';
            if(file_exists($path))
            {
                $fh = fopen($path, 'r');
                $matches = array();
                while($zeile = fgets($fh))
                {
                    if(preg_match('/^([^;\s]+)\s*=([^;]*).*$/', $zeile, $matches))
                    {
                        $this->lng_strings[$matches[1]] = $matches[2];
                    }
                }
            }
            else
            {
                throw new Exception("language file '$path' doesnt exist");
            }
        }
 
        /**
          * Gibt einen Sprachstring zurück.
          * @param string $name Der Name des Sprachstrings.
          */
        function get($name)
        {
            return $this->lng_strings[$name];
        }
 
    };
 
    /*
     -> singleton pattern
     (immer nur eine einzige Instanz dieser Klasse zulassen;
     Instanz(en) werden ausschließlich über diese Funktion erstellt)
    */
    function getLangInstance($language) {
        static $lang_instance;
 
        if(!is_object($lang_instance)) {
            global $obst;
            $lang_instance = new Lang();
            $lang_instance->load_lng($language);
        }
 
        return $lang_instance;
    }
?>

Die angepasste Smarty-Klasse nenne ich LangSmarty. Dem Konstruktor muss natürlich eine Instanz der Klasse Lang übergeben werden. Alles Weitere steht in den Kommentaren.

<?php
    /**
      * @author: Robert Nitsch
      */
    class LangSmarty extends Smarty {
 
        /**
            * Speichert die Instanz der Klasse Lang ab.
            * @var Lang Instanz der Klasse Lang
            */
        protected $lang;
 
        /**
          * Konstruktor
          * @param Lang $lang Eine Instanz der Klasse Lang, die die jeweiligen Sprachdaten beinhaltet.
          */
        function LangSmarty(Lang &$lang)
        {
            $this->lang = $lang;
 
            // muss an das jeweilige Webprojekt angepasst werden
            $this->template_dir = './template_dir';
            $this->compile_dir = './compile_dir';
            $this->cache_dir = './cache_dir';
 
            // den prefilter registrieren
            $this->register_prefilter(array(&$this, 'translate_prefilter'));
        }
 
        /**
          * Diese sogenannte Callback-Funktion wird für jedes Auftauchen von ##(...)## innerhalb von Templates
          * aufgerufen und gibt die jeweilige Ersetzung zurück.
          * @param string $string Die konkrete Übereinstimmung. Z.B. "##user_form_username##"
          */
        function _translate_callback($string) {
            $string = substr($string[0], 2, strlen($string[0])-4);
            return $this->lang->get($string);
        }
 
        /**
          * Dies ist der Smarty-Prefilter. Er führt die Ersetzung der Sprachstrings ##(...)##
          * durch die Callback-Funktion _translate_callback() durch.
          */
        function translate_prefilter($tpl_source, &$smarty) {
            return preg_replace_callback("/##[^#]*##/", array(&$this, '_translate_callback'), $tpl_source);
        }
 
        /**
          * Jede sprache soll einen eigenen Satz an Compiled- und Cached-Files haben.
          * Dafür wird das jeweilige  Länderkürzel einfach in den Dateinamen dieser Dateien eingebaut.
          */
        function fetch($_smarty_tpl_file, $_smarty_cache_id = null, $_smarty_compile_id = null, $_smarty_display = false) {
 
            $_smarty_compile_id = $this->lang->lng.$_smarty_compile_id;
            $_smarty_cache_id = $_smarty_compile_id;
            return parent::fetch( $_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id, $_smarty_display);
        }
 
    }
?>

Anwendung:

<?php
  $lang = getLangInstance('de'); // instanz der Lang-Klasse erstellen
  $smarty = new LangSmarty($lang); // instanz der LangSmarty-Klasse erstellen, dem Konstruktor wird eine Lang-Instanz übergeben
  $smarty->display('startseite.tpl'); // das Template startseite.tpl kompilieren und das Ergebnis dessen Ausführung (ja, bei Smarty werden Templates ausgeführt) an den Client schicken
  // Sprachstrings werden automatisch eingefügt, sofern im Template vorhanden
?>

Das Ganze lässt sich aber noch optimieren. Die Lösung mit den oben gezeigten Sprachdateien ist zwar einfach, aber bei großen Projekten kann es in viel Arbeit ausarten alle Sprachdateien aktuell zu halten. Auch wenn man diese Arbeit an freiwillige Übersetzer delegieren kann, so empfiehlt es sich, das System generell zu verbessern.

Dies kann mit Hilfe von gettext erreicht werden.

Zitat Wikipedia:

GNU gettext ist die GNU-Internationalisierungsbibliothek. Normalerweise wird sie zur Entwicklung von mehrsprachigen Programmen genutzt. Die derzeit aktuelle Version ist 0.16.1.

Und ja, auch in PHP kann man auf gettext zugreifen: Gettext-Funktionen

Dank der objektorientieren Programmierung müssen wir nur die Klasse Lang an gettext anpassen. Das werde ich in diesem Artikel aber nicht mehr bis ins Detail erklären. Letztendlich läuft es darauf hinaus, dass man nur ein paar Zeilen verändert (nicht aber wesentliche Strukturen).

Der größte Aufwand steckt allerdings in der Gewöhnung an gettext. Bevor man auf gettext umsteigt, sollte man sich daher etwas einlesen. Mir hat der Text ONLamp.com — Gettext sehr gefallen…

Code herunterladen

Es macht keinen Sinn, den Code direkt aus dem Beitrag herauszukopieren, da verschiedene Zeichen von Wordpress ungünstig umgewandelt werden. Daher hier der Download des ZIP-Archivs mit den Klassen Lang und LangSmarty:

PHP-Smarty-Mehrsprachig.zip herunterladen

Hinweis: Die Dateien sind UTF-8-kodiert. Linux-Benutzer werden damit keine Probleme haben, Windows-Nutzer sollten auf den Notepad++ – Editor zurückgreifen.

20.05.2007

Status des KDE-Bildschirmschoners abfragen

Abgelegt unter: Kubuntu, Linux, Python, Snippets — Robert @ 19:03:16

Der Status des KDE-Bildschirmschoners lässt sich per Kommandozeile mit diesem Befehl abfragen:

robert@zeus:~$ dcop kdesktop KScreensaverIface isBlanked
false

dcop ist das Kommandozeilenprogramm mit dem man Zugriff auf die DCOP-Schnittstellen (Desktop Communication Protocoll) beliebiger Programme erhält. kdesktop ist eine davon. Und über das KScreensaverIface lässt sich der Bildschirmschoner steuern, bzw. in diesem Fall wird abgefragt, ob er momentan aktiv ist.

In Python sieht das Ganze dann so aus:

import os
 
def screensaverActivated():
    """
    checks whether the screensaver is currently activated
    """
    return (os.popen('dcop kdesktop KScreensaverIface isBlanked', 'r').readline().strip() == 'true')

Allerdings wird diese Lösung nicht auf Dauer funktionieren. Zitat aus dem Wikipedia-Artikel:

KDE 4 wird DCOP nicht mehr enthalten. Stattdessen wird dann das von spezifischen Desktopumgebungen unabhängige D-Bus verwendet.

18.05.2007

Künstliche Intelligenzen gegeneinander kämpfen lassen…

Abgelegt unter: Informatik, Spiele — Robert @ 22:09:47

AntMe! Logo
© Microsoft

Heute morgen habe ich von “AntMe!” gelesen, einer Programmierumgebung, die man innerhalb von Microsoft®s .NET-Programmierumgebungen (IDEs) nutzen kann. Anscheinend kann man die zur Nutzung von “AntMe!” nötige Funktionalität nachträglich in eine beliebgie .NET-IDE integrieren (als Plugin o.ä.). Dann kann man offenbar (ich habe es noch nicht selbst probiert) die “Programmierung” eines Ameisenvolkes vornehmen, das im Anschluss gegen beliebige andere Ameisenvölker unterschiedlicher Programmierung antreten kann. Es gilt, Nahrung zu sammeln und ggf. zu kämpfen – nur einer übersteht auf Dauer die Konkurrenz. Der “Kampf” wird natürlich visuell veranschaulicht und man kann seinen Ameisen live beim Kampf zusehen:

AntMe! Visualisierung
© Microsoft

Was mich daran besonders fasziniert, ist die Idee, gegen die Ameisenvölker anderer Programmierer anzutreten und das eigene Ameisenvolk von mal zu mal zu perfektionieren. Im Moment bin ich deswegen dabei, Microsoft® Visual C#® 2005 Express herunterzuladen und zu installieren.

Alternativen zu AntMe!

Bis gerade eben habe ich Alternativen zu AntMe! ausprobiert – für Linux. Erstmal war es aufwändig, welche zu finden… dann fand ich Seed7 bzw. DNA Fight, konnte es aber leider trotz mehrerer Versuche nicht kompilieren (typisches Problem) konnte es aber erst nach dem Fertigstellen dieses Artikels erfolgreich kompilieren. Siehe dazu Kommentar 3 und folgende…
Nebenbei fand ich allerdings auch NERO, womit nicht das allseits bekannte Brennprogramm, sondern ein Spiel mit ähnlichen Funktionen und einem ähnlichen Spielziel, wie dem von AntMe!. Allerdings konnte ich auch hier die Linux-Version nicht zum Laufen bringen (diesmal konnte mein Packer das Archiv nicht entpacken)…

Wikipedia:

Im Spiel trainiert man Roboter, die für ihr Handeln vom Spieler entweder eine positive oder eine negative Bewertung bekommen. Gut bewertete virtuelle Genkonfigurationen haben eine höhere Chance, bei der simulierten Evolution mit einem anderen Gen verschmolzen zu werden.

> Offizielle Website von NERO

KI-Schlachtfelder mit dem Warcraft® 3 – Welteneditor…

Zum Abschluss hatte ich die Idee, ein Strategiespiel für mein Ziel zu zweckentfremden … dazu nahm ich Warcraft® 3 (bzw. die Erweiterung TFT) und erstellte mir im Welten-Editor eine sehr triviale Karte mit dem Titel “AI Wars” (deutsch: Krieg der künstlichen Intelligenzen), auf der 2 Spieler gegeneinander antreten konnten. Die 2 “Spieler” sind in Wirklichkeit die beiden künstlichen “Intelligenzen”. Man selbst kann nur zusehen (dafür gibt es 3 weitere Spielerslots).

Das Hauptfenster des Editors:
AI Wars - Editor

Damit auch überhaupt ein Kampf stattfinden kann, starten die KIs mit jeweils 15 Gold. Alle 15 Sekunden wird das vorhandene Gold in neue Einheiten umgesetzt. Die erste KI hat dabei ihr Lager in der oberen linken Ecke der Karte, die zweite KI genau gegenüber. Überdies hinaus haben beide KIs in ihrem Lager einen Brunnen der Gesundheit, an dem Einheiten sehr schnell geheilt werden können, falls sie verletzt sind.

Die KIs agieren hauptsächlich über einen Code, der alle 15 Sekunden ausgeführt wird. Dieser Code veranlasst z.B. alle Einheiten in der jeweiligen Basis zum aggressiven Vorrücken zur Basis des jeweiligen Gegners. Am Anfang rennen also sofort alle 15 Soldaten los und treffen sich in der Mitte mit den Einheiten des Kontrahenten. Da ein einzelner Code, der alle 15 Sekunden aufgerufen wird, allerdings weder eine ordentliche Programmierung zulässt, noch sehr viele Möglichkeiten zulässt, kann man natürlich jeder KI auch weitere “Auslöser” verpassen.

Ein Auslöser im Welten-Editor ist eine Einheit von Ereignissen, Bedingungen und Aktionen … als Ereignis kann z.B. ein Timer gelten, der alle X Sekunden ausgelöst wird. Im Welten-Editor von Warcraft® sind aber schier unendlich viele Möglichkeiten vorhanden… so kann man auch auf das “Angegriffenwerden” einer Einheit reagieren… und diese Einheit dann zum Gegenangriff oder Fliehen veranlassen. Wann immer ich also in diesem Artikel von einem “Code” rede, meine ich in Wirklichkeit einen oder mehrere Auslöser…

Der Auslöser-Editor:
AI Wars - Auslöser

Übrigens erhält eine KI für jeden feindlichen Soldaten, den sie tötet, 1 Gold, d.h. für jeden Soldaten, der getötet wird, erhält die KI einen eigenen Soldaten dazu. Damit eine KI deswegen nicht allzu schnell gewinnt, haben beide KIs in ihrem Lager einen starken Wachturm, der auf feindliche Einheiten schiesst und selbst sehr viel Leben (“Trefferpunkte”) hat. Es dauert also ein paar Minuten, bis sich das Spiel entschieden hat. Außerdem wird diese Karte von mir noch stark erweitert werden, sodass die KIs sich dann nicht nur bekämpfen müssen, sondern auch eine kleine Wirtschaft steuern müssen können. Dann relativieren sich die kleinen Vorteile beim Spielbeginn (wer zuerst zuschlägt usw.).

Das erste Anzeichen von Intelligenz…

Damit auch ich als Programmierer meinen Spaß habe, habe ich mir sogleich eine etwas überlegene KI geschaffen. Sie verfährt nicht mehr nur nach dem “Haudrauf”-Prinzip … jedes Mal, wenn eine Einheit einen Schlag kassiert, wird überprüft, ob diese Einheit noch mindestens 20% ihrer Trefferpunkte besitzt. Falls nicht, so rennt sie sofort zurück in die Basis zum “Brunnen des Lebens”, wo sie dann eine sehr hohe Regenerationsrate genießt. Sobald ihre Trefferpunkte auf über 90% der maximalen TP angestiegen sind, nimmt sie wieder an der Schlacht teil.
Dadurch werden gleich zwei Dinge erreicht: in der Basis steht immer eine kleine “Reserve”, die im Falle des Angriffes – obwohl sie natürlich geschwächt ist – dem Gegner etwas Kontra geben kann …
Außerdem, und das ist viel wichtiger, gelingt es der gegnerischen KI nun fast überhaupt nicht mehr, irgendwelche Soldaten zu töten. Damit versiegt also, zumindest in der jetzigen Version von “AI Wars”, die Hauptgeldquelle des Gegners…

Vorteile und Nachteile des Welteneditors für KI-Schlachten

Vorteile:

  • nahezu unendlich viele Möglichkeiten der KI-Programmierung
  • ebenfalls unendlich viele Möglichkeiten beim Gestalten der Umgebung/der Umstände
  • eine äußerst schöne grafische Darstellung der Schlachten ;-)

Nachteile:

  • beide Programmierer benötigen das Originalspiel
  • das Spiel ist nicht plattformunabhängig (es läuft aber dennoch nativ sowohl auf Windows®- als auch Mac-Systemen)
  • das Antreten zweier KIs ist etwas schwerfällig: man muss jedes Mal, wenn man 2 KIs gegeneinander kämpfen lassen möchte, die Karte editieren (die Auslöser des anderen Programmierers importieren und die eigene KI hineinepfriemeln…)

Solange es keine bessere Lösung gibt, werde ich diese Methode weiter ausfeilen und die Möglichkeiten des Warcraft – Welteneditors so gut es geht ausschöpfen. Da ich bisher nicht eine einzige Karte mit diesem Editor erstellt habe, ist das auch gleich eine gute Gelegenheit für mich, mich damit auseinanderzusetzen. Und wie man an den Screenshots und meinem Gerede erkennt, ist es gar nicht so schwer, wenn man sich ein paar Stunden Zeit nimmt (oder sie einfach hat).

Download der Karte

Für diejenigen, die bis hierher gelesen haben und sich trotzdem im Besitz von Warcraft 3 (+Erweiterung TFT!) befinden, biete ich meine Warcraft-Karte zum Download an. So könnt ihr auch gleich sehen, wie ich bestimmte Dinge realisiert habe. Ich würde mir sowas als Leser auf jeden Fall wünschen. ;)

Download: AI Wars 1.00

Wer eine KI schreibt, die gegen meine KI gewinnt (“KI 1″ bzw. Spieler 11 stellt meine KI dar, KI 2 ist die dumme “Haudrauf”-Truppe), der darf mir seine KI natürlich gerne zukommen lassen. Dann werde ich meinerseits eine bessere KI schreiben und sie zurückschicken. ;)

AI Wars mit Python

Ich träume im Moment von einem Python-Modul, mit dem man AI Wars ausführen kann – 2 ambitionierte Pythonprogrammierer lassen dann ihre künstlichen Intelligenzen gegeneinander antreten – das Modul gibt lediglich die Schnittstelle und damit auch die Spielregeln vor… außerdem ermöglicht es auch gleich die grafische Ansicht des “Kampfes”.

Innerhalb der nächsten Tage werde ich mich diesem noch nicht existenten Modul mal annehmen … zuerst überlege ich mir aber ein geeignetes Szenario für meine “AI Wars”. ;) Für Vorschläge bin ich jederzeit offen – ich würde mich sehr über Ideen und Ratschläge freuen!

Seite:  1 2 Nächste Seite
Nächste Seite »

© Robert Nitsch
(Powered by WordPress ( WordPress Deutschland ))