[Quiz#9] kuddeldaddeldu (PHP)


kuddeldaddeldu

Erfahrenes Mitglied
#1
Hi,

ich hatte zwar wenig Zeit und bin nicht fertig, aber angesichts der Uhrzeit stelle ich schon mal die bisherige Lösung rein. Bei Aufgabe B fehlen noch die Erweiterungen.

Basisklassen, die von allen Turtles genutzt, bzw. erweitert werden:

Plotter.php (abstrakte Klasse)
PHP:
<?php
abstract class Plotter {
   
   protected $name;
   protected $width;
   protected $height;
   protected $buffer;
   
   public function __construct($name, $width, $height) {
      $this->name = $name;
      $this->height = $height;
      $this->width = $width;
   }
   
   abstract public function draw_line($x1, $y1, $x2, $y2);
   abstract public function draw_polyline($line);
}
?>

SVGPlotter.php (konkrete SVG-Implementierung der Klasse Plotter
PHP:
<?php
require_once 'Plotter.php';

class SVGPlotter extends Plotter {
      
   private $doc_tpl = '<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{width}" height="{height}" stroke="black" fill="none">
{content}
</svg>';
   private $line_tpl = '<line x1="%d" y1="%d" x2="%d" y2="%d" />';
   private $polyline_tpl = '<polyline points="%s" />';
   private $svg = '';
  
   public function __construct($name, $width, $height) {
      parent::__construct($name, $width, $height);
      $this->svg = str_replace(array('{width}', '{height}'), array($width, $height), $this->doc_tpl);
      $this->buffer = '';
   }
   
   public function __destruct() {
      $this->svg = str_replace('{content}', $this->buffer, $this->svg);
      file_put_contents($this->name . '.svg', $this->svg);
   }
   
   public function draw_line($x1, $y1, $x2, $y2) {
      $this->buffer .= sprintf($this->line_tpl, $x1, $this->height - $y1, $x2, $this->height - $y2) . "\n";
   }
   
   public function draw_polyline($coordinates) {
      $points = array();
      foreach($coordinates as $point) {
         $points[] = sprintf('%F %F', $point[0], $this->height - $point[1]);
      }
      $this->buffer .= sprintf($this->polyline_tpl, implode(',', $points));
   }
} 
?>

Factory.php (liefert eine Instanz des konkret benötigten Plotters)
PHP:
<?php
class Factory {
   
   public static function getPlotter($type, $target, $width, $height) {
      switch($type) {
         case 'svg':
            $classname = 'SVGPlotter';
            break;
      }
      require_once($classname . '.php');
      return new $classname($target, $width, $height);
   }
}
?>

Turtle.php (abstrakte Klasse)
PHP:
<?php

abstract class Turtle {
   protected $phi = 0;
   protected $angle;
   protected $x;
   protected $y;
   protected $stepwidth;
   protected $actions;
   protected $name;
   protected $width;
   protected $height;

   abstract public function parseInput($input);
   abstract public function run();
   
   public function __construct($inputFile) {
      $this->parseInput(file_get_contents($inputFile));
      $this->setName(basename($inputFile, '.turtle'));
   }
   
   protected function getName() {
      return $this->name;
   }
   protected function getX() {
      return $this->x;
   }
   protected function getY() {
      return $this->y;
   }
   protected function getPhi() {
      return $this->phi;
   }
   protected function getAngle() {
      return $this->angle;
   }
   protected function getStepwidth() {
      return $this->stepwidth;
   }
   protected function getActions() {
      return $this->actions;
   }
   protected function getWidth() {
      return $this->width;
   }
   protected function getHeight() {
      return $this->height;
   }
   protected function setName($name) {
      $this->name = $name;
   }
   protected function setX($x) {
      $this->x = $x;
   }
   protected function setY($y) {
      $this->y = $y;
   }
   protected function setPhi($phi) {
      $this->phi = $phi;
   }
   protected function setAngle($angle) {
      $this->angle = $angle * M_PI / 180.0;
   }
   protected function setStepwidth($stepwidth) {
      $this->stepwidth = $stepwidth;
   }
   protected function setActions($actions) {
      $this->actions = $actions;
   }
   protected function setWidth($width) {
      $this->width = $width;
   }
   protected function setHeight($height) {
      $this->height = $height;
   }
   protected function incrementPhi() {
      $this->setPhi($this->getPhi() + $this->getAngle());
   }
   protected function decrementPhi() {
      $this->setPhi($this->getPhi() - $this->getAngle());
   }
}
?>

Aufgabe A:

TurtleA.php (konkrete Implementierung von Turtle)
PHP:
<?php
require_once 'Turtle.php';
require_once 'Factory.php';

class TurtleA extends Turtle {
   
   public function parseInput($input) {
      $params = sscanf($input, "%d %d\n%d %d\n%d\n%d\n%s");
      $this->setWidth($params[0]);
      $this->setHeight($params[1]);
      $this->setX($params[2]);
      $this->setY($params[3]);
      $this->setStepwidth($params[4]);
      $this->setAngle($params[5]);
      $this->setPhi(0);
      $this->setActions(str_split($params[6]));
   }
   
   public function run() {
      $plotter = Factory::getPlotter('svg', $this->getName(), $this->getWidth(), $this->getHeight());
      foreach($this->getActions() as $action) {
         switch($action) {
            case 'F':
               $plotter->draw_line($this->getX(), $this->getY(), $this->getX() + $this->getStepwidth()*cos($this->getPhi()), $this->getY() + $this->getStepwidth()*sin($this->getPhi()));
               $this->setX($this->getX() + $this->getStepwidth()*cos($this->getPhi()));
               $this->setY($this->getY() + $this->getStepwidth()*sin($this->getPhi()));
               break;
            case '+':
               $this->incrementPhi();
               break;
            case '-':
               $this->decrementPhi();
               break;
         }
      }
   }
}

$turtle = new TurtleA($argv[1]);
$turtle->run();

?>

Parameter ist der Pfad auf die Turtle-Datei.

Erweiterung:

TurleA_ext.php (konkrete Implementierung von Turtle)
PHP:
<?php
require_once 'Turtle.php';
require_once 'Factory.php';

class TurtleA extends Turtle {
   
   private $stack;
   private $pos;
   
   private function getPos() {
      return $this->pos;
   }
   
   private function setPos($pos) {
      $this->pos = $pos;
   }
   
   private function incrementPos() {
      $this->pos++;
   }

   private function setStack($stack) {
      $this->stack = $stack;
   }
   
   private function getAction($pos) {
      return $this->actions[$pos];
   }
   
   private function pushStack() {
      $this->stack[] = array($this->getX(), $this->getY(), $this->getPhi());
   }
   
   private function popStack() {
      $state = array_pop($this->stack);
      $this->setX($state[0]);
      $this->setY($state[1]);
      $this->setPhi($state[2]);
   }
  
   public function __construct($inputFile) {
      parent::__construct($inputFile);
      $this->setStack(array());
      $this->setPos(0);
   }
   
   public function parseInput($input) {
      $params = sscanf($input, "%d %d\n%d %d\n%d\n%d\n%s");
      $this->setWidth($params[0]);
      $this->setHeight($params[1]);
      $this->setX($params[2]);
      $this->setY($params[3]);
      $this->setStepwidth($params[4]);
      $this->setAngle($params[5]);
      $this->setPhi(0);
      $this->setActions(str_split($params[6]));
   }
   
   public function run() {
      $plotter = Factory::getPlotter('svg', $this->getName(), $this->getWidth(), $this->getHeight());
      while($this->getPos() < count($this->actions)) {
         switch($this->getAction($this->getPos())) {
            case '[':
               $this->pushStack();
               break;
            case ']':
               $this->popStack();
               break;
            case 'F':
               $plotter->draw_line($this->getX(), $this->getY(), $this->getX() + $this->getStepwidth()*cos($this->getPhi()), $this->getY() + $this->getStepwidth()*sin($this->getPhi()));
               $this->setX($this->getX() + $this->getStepwidth()*cos($this->getPhi()));
               $this->setY($this->getY() + $this->getStepwidth()*sin($this->getPhi()));
               break;
            case '+':
               $this->incrementPhi();
               break;
            case '-':
               $this->decrementPhi();
               break;
         }
         $this->incrementPos();
      }
   }
}

$turtle = new TurtleA($argv[1]);
$turtle->run();
?>

Aufgabe B:

TurtleB.php (konkrete Implementierung von Turtle)
PHP:
<?php
require_once 'Turtle.php';
require_once 'Factory.php';

class TurtleB extends Turtle {
   
   private $stack;
   private $pos;
   private $polyline;
   private $plotter;
   private $substitutions;
   
   private function setSubstitutions($substitutions) {
      $this->substitutions = $substitutions;
   }
   
   private function getPos() {
      return $this->pos;
   }
   
   private function setPos($pos) {
      $this->pos = $pos;
   }
   
   private function setStack($stack) {
      $this->stack = $stack;
   }
   
   private function getPolyline() {
      return $this->polyline;
   }
   
   private function getPolylineSize() {
      return count($this->getPolyline());
   }
 
   private function resetPolyline() {
      $this->polyline = array();
   }
      
   private function pushPolyline($x, $y) {
      $this->polyline[] = array($x, $y);
   }

   private function incrementPos() {
      $this->pos++;
   }
   
   private function getAction($pos) {
      return $this->actions[$pos];
   }
   
   private function pushStack() {
      $this->stack[] = array($this->getX(), $this->getY(), $this->getPhi());
   }
   
   private function popStack() {
      $state = array_pop($this->stack);
      $this->setX($state[0]);
      $this->setY($state[1]);
      $this->setPhi($state[2]);
   }
  
   public function __construct($inputFile) {
      parent::__construct($inputFile);
      $this->setStack(array());
      $this->resetPolyline();
      $this->pushPolyline($this->getX(), $this->getY());
      $this->setPos(0);
   }
   
   public function parseInput($input) {
      $params = sscanf($input, "%d %d\n%f %f\n%f\n%f\n%s\n%d\n%s");
      $this->setWidth($params[0]);
      $this->setHeight($params[1]);
      $this->setX($params[2]);
      $this->setY($params[3]);
      $this->setStepwidth($params[4]);
      $this->setAngle($params[5]);
      $iterations = $params[7];
      $substitution = $params[8];
      $actions = $params[6];
      for($i = 0; $i < $iterations; $i++) {
         $actions = str_replace('F', $substitution, $actions);
      }
      $this->setPhi(0);
      $this->setActions(str_split($actions));
   }
   
   public function run() {
      $this->plotter = Factory::getPlotter('png', $this->getName(), $this->getWidth(), $this->getHeight());
      while($this->getPos() < count($this->actions)) {
         switch($this->getAction($this->getPos())) {
            case '[':
               $this->pushStack();
               break;
            case ']':
               if($this->getPolyline()) {
                  $this->plotter->draw_polyline($this->getPolyline());
               }
               $this->popStack();
               $this->resetPolyline();
               $this->pushPolyline($this->getX(), $this->getY());
               break;
            case 'F':
               $this->setX($this->getX() + $this->getStepwidth()*cos($this->getPhi()));
               $this->setY($this->getY() + $this->getStepwidth()*sin($this->getPhi()));
               $this->pushPolyline($this->getX(), $this->getY());
               if($this->getPolylineSize() > 50) {
                  $this->plotter->draw_polyline($this->getPolyline()); 
                  $this->resetPolyline();
                  $this->pushPolyline($this->getX(), $this->getY());
               }
               break;
            case '+':
               $this->incrementPhi();
               break;
            case '-':
               $this->decrementPhi();
               break;
         }
         $this->incrementPos();
      }
      if($this->getPolylineSize() > 0) {
         $this->plotter->draw_polyline($this->getPolyline()); 
      }
   }
}

$turtle = new TurtleB($argv[1]);
$turtle->run();
?>

Vielleicht liefer ich die Erweiterungen noch nach.

LG
 
Zuletzt bearbeitet:

kuddeldaddeldu

Erfahrenes Mitglied
#2
So... da sich ja noch nichts getan hat, kommt jetzt der Rest:

Aufgabe B1:

Die Erweiterung unterscheidet sich nur in der parseInput()-Methode:

PHP:
<?php
   public function parseInput($input) {
      $params = sscanf(trim($input), "%d %d\n%f %f\n%f\n%f\n%s\n%d\n%[^$]s");
      $this->setWidth($params[0]);
      $this->setHeight($params[1]);
      $this->setX($params[2]);
      $this->setY($params[3]);
      $this->setStepwidth($params[4]);
      $this->setAngle($params[5]);
      $iterations = $params[7];
      $helper = explode("\n", $params[8]);
      $symbols = array();
      $substitutions = array();
      // Regeln in ein Array [Symbol] => [Ersetzungsliste] packen
      foreach($helper as $value) {
         $substitutions[substr($value, 0, strpos($value, ' '))] = substr($value, strpos($value, ' ') + 1);
      }
      $actions = $params[6];
      for($i = 0; $i < $iterations; $i++) {
         // simultanes Ersetzen mit strtr()
         $actions = strtr($actions, $substitutions);
         //echo $i+1 . ": $actions\n";     // für Log-Vergleiche
      }
      $this->setPhi(0);
      $this->setActions(str_split($actions));
   }
?>
Ein deutliches Zeichen dafür, dass man hier noch mehr aufsplitten sollte. Die Turtle-Klasse sollte eigentlich für nichts anderes verantwortlich sein, als ein fertig generiertes Eingabewort abzulaufen.

Aufgabe B2:

Da war das Fummeligste natürlich das simultane Ersetzen mit jeweils neuem Zufallswert für jede einzelne Ersetzung.
Außerdem habe ich noch etwas rumgespielt, indem ich die Plotterinstanz jetzt in einer eigenen Methode hole, die den Typen übergeben bekommt (2. Kommandozeielnparameter). Dementsprechend wurde die Factory erweitert und als Beispiel ein PNGPlotter ergänzt:

Factory.php
PHP:
<?php
class Factory {
   
   public static function getPlotter($type, $target, $width, $height) {
      switch($type) {
         case 'svg':
            $classname = 'SVGPlotter';
            break;
         case 'png':
            $classname = 'PNGPlotter';
            break;
      }
      require_once($classname . '.php');
      return new $classname($target, $width, $height);
   }
}
?>

PNGPlotter.php
PHP:
<?php
require_once 'Plotter.php';

class PNGPlotter extends Plotter {

   private $image;
   private $color;
  
   public function __construct($name, $width, $height) {
      parent::__construct($name, $width, $height);
      $this->image = imagecreatetruecolor($width, $height);
      imagefill($this->image, 0, 0, ImageColorAllocate($this->image, 255, 255, 255));
      $this->color = ImageColorAllocate($this->image, 0, 0, 0);
   }
   
   public function __destruct() {
      imagepng($this->image, $this->name . '.png');
   }
   
   public function draw_line($x1, $y1, $x2, $y2) {
      imageline($this->image, $x1, $this->height - $y1, $x2, $this->height - $y2, $this->color);
   }
   
   public function draw_polyline($coordinates) {
      for($i = 0; $i < count($coordinates) - 1; $i++) {
         $this->draw_line($coordinates[$i][0],$coordinates[$i][1],$coordinates[$i+1][0],$coordinates[$i+1][1]);
      }
   }
} 
?>

TurtleB_ext2.php
PHP:
<?php
require_once 'Turtle.php';
require_once 'Factory.php';

class TurtleB extends Turtle {
   
   private $stack;
   private $pos;
   private $polyline;
   private $plotter;
   private $substitutions;
   
   private function setSubstitutions($substitutions) {
      $this->substitutions = $substitutions;
   }

   private function getPos() {
      return $this->pos;
   }
   
   private function setPos($pos) {
      $this->pos = $pos;
   }
   
   private function setStack($stack) {
      $this->stack = $stack;
   }
   
   private function getPolyline() {
      return $this->polyline;
   }
   
   private function getPolylineSize() {
      return count($this->getPolyline());
   }
   
   private function resetPolyline() {
      $this->polyline = array();
   }
      
   private function pushPolyline($x, $y) {
      $this->polyline[] = array($x, $y);
   }

   private function incrementPos() {
      $this->pos++;
   }
   
   private function getAction($pos) {
      return $this->actions[$pos];
   }
   
   // liefert alle Ersetzungsregeln für ein gegebenes Symbol
   private function getSubstitutes($symbol) {
      return $this->substitutions[$symbol];
   }
   
   private function pushStack() {
      $this->stack[] = array($this->getX(), $this->getY(), $this->getPhi());
   }
   
   private function popStack() {
      $state = array_pop($this->stack);
      $this->setX($state[0]);
      $this->setY($state[1]);
      $this->setPhi($state[2]);
   }
   
   /***** Callback-Funktion, die für das gegebene Symbol eine Ersetzungsregel *****/
   /***** zufällig auswählt (unter Berücksichtigung der Gewichte)             *****/
   private function random_substitute($token) {
      $sum = 0;
      $substitutes = $this->getSubstitutes($token[0]);
      $sum_probabilities = array_sum($substitutes);
      $random = mt_rand(0, $sum_probabilities);
      foreach ($substitutes as $candidate => $probability) {
         $sum += floatVal($probability);
         if ($sum >= $random) {
            return $candidate;
         }
      }
   } 
   
   public function __construct($inputFile) {
      parent::__construct($inputFile);
      $this->setStack(array());
      $this->resetPolyline();
      $this->pushPolyline($this->getX(), $this->getY());
      $this->setPos(0);
   }
   
   public function parseInput($input) {
      $params = sscanf(trim($input), "%d %d\n%f %f\n%f\n%f\n%s\n%d\n%[^$]s");
      $this->setWidth($params[0]);
      $this->setHeight($params[1]);
      $this->setX($params[2]);
      $this->setY($params[3]);
      $this->setStepwidth($params[4]);
      $this->setAngle($params[5]);
      $iterations = $params[7];
      $rules = explode("\n", $params[8]);
      $substitutions = array();
      $symbols = array();
      foreach($rules as $value) {
         $rule = explode(' ', $value);
         $substitutions[$rule[0]][$rule[2]] = floatVal($rule[1]);
         // Symbole für Ersetzungsfunktion sammeln
         if(! in_array($rule[0], $symbols)) {
            $symbols[] = $rule[0];
         }
      }
      $this->setSubstitutions($substitutions);
      // RegExp-Delimiters einsetzen
      $search_exps = array_map(create_function('$a', 'return "/$a/";'), $symbols);
      $actions = $params[6];
      for($i = 0; $i < $iterations; $i++) {
         /*** Es sind zwar keine regulären Ausdrücke zum Suchen erfoderlich, aber ***/ 
         /*** die Callback-Funktion ermöglicht simultanes Ersetzen mit neuem      ***/
         /*** Zufallswert für jedes gefundene Symbol.                             ***/
         $actions = preg_replace_callback($search_exps, array( &$this, 'random_substitute'), $actions);
         // echo $i+1 . ": $actions\n";     // für Log-Vergleiche
      }
      $this->setPhi(0);
      $this->setActions(str_split($actions));
   }
   
   public function setPlotter($type) {
      $this->plotter = Factory::getPlotter($type, $this->getName(), $this->getWidth(), $this->getHeight());
   }
   
   public function run() {
      while($this->getPos() < count($this->actions)) {
         switch($this->getAction($this->getPos())) {
            case '[':
               $this->pushStack();
               break;
            case ']':
               // Vor dem "Zurücklaufen" muss der aktuelle Linienzug gezeichnet werden.
               if($this->getPolyline()) {
                 $this->plotter->draw_polyline($this->getPolyline());
               }
               $this->popStack();
               // Punktliste leeren und Startpunkt für den nächsten Linienzug setzen
               $this->resetPolyline();
               $this->pushPolyline($this->getX(), $this->getY());
               break;
            case 'F':
               $this->setX($this->getX() + $this->getStepwidth()*cos($this->getPhi()));
               $this->setY($this->getY() + $this->getStepwidth()*sin($this->getPhi()));
               $this->pushPolyline($this->getX(), $this->getY());
               /*** Um zu vermeiden, dass der Speicher volläuft, werden Linienzüge nur ***/
               /*** bis zu einer bestimmten Anzahl Punkte aufgebaut, dann erfolgt ein  ***/
               /*** "flush". 50 ist völlig willkürlich gewählt                         ***/
               if($this->getPolylineSize() > 50) {
                  $this->plotter->draw_polyline($this->getPolyline()); 
                  $this->resetPolyline();
                  $this->pushPolyline($this->getX(), $this->getY());
               }
               break;
            case '+':
               $this->incrementPhi();
               break;
            case '-':
               $this->decrementPhi();
               break;
            default:
               break;
         }
         $this->incrementPos();
      }
      // restlichen Linienzug zeichnen
      if($this->getPolylineSize() > 0) {
         $this->plotter->draw_polyline($this->getPolyline()); 
      }
   }
}

$turtle = new TurtleB($argv[1]);
$turtle->setPlotter($argv[2]);
$turtle->run();
?>

Aufruf:
Code:
shell> php Turtle_ext2.php path/to/turtle/file.turtle svg|png
LG
 

Anhänge

kuddeldaddeldu

Erfahrenes Mitglied
#3
Hi,

ich habe mal mit PHP-GTK gespielt. Das Ergebnis befindet sich im Anhang, ist noch sehr buggy und unaufgeräumt, aber ich habe bis zum Wochenende keine Zeit mehr. Wer Interesse hat: man braucht natürlich PHP-GTK und außerdem libglade.

Und seid bitte gnädig, das war mein erster Versuch in puncto GUI-Programmierung... :-( ^^

LG
 

Anhänge