SteffenBoerner
Mitglied
Hier also meine Lösung in C#. Hier ist alles bis einschliesslich zur Erweiterung B2 implementiert und Die Beispiele aus dem Forum funktionieren alle.
Hier kommt erstmal die Hauptklasse 'Turtle':
Mit der Funktion Initialize() wird der 'Schildkröte' mitgeteilt, was sie alles machen soll, sprich der Code eingelesen.
Die Funktion Paint() übernimmt dann das eigentliche Zeichnen, d.h. sie erzeugt eine Liste von 'Multilines', die wie folgt definiert sind:
Diese Klasse ist eigentlich nichts anderes als eine Liste von float-Koordinaten sowie einem aktuellen Blickwinkel, also der Blickrichtung der 'Schildkröte' am letzten Punkt dieser Liste.
Die Funktion AddStartPoint() fügt den Anfangspunkt in die Liste ein, während DrawLine() eine Linie mit der im Parameter übergebenen Länge ab dem letzt Punkt in die aktuelle Blickrichtung zeichnet, d.h. den Endpunkt dieser Linie in die Liste einfügt.
Die iterative Ersetzung wird durch die Funktion GetActionList() realisiert, die von der Funktion Paint() [über die Property _ActionList] aufgerufen wird. Die Iteration wird dabei durch rekursive Aufrufe von GetActionList() mit rückwärtslaufendem Iterationszähler als Parameter erreicht; Abbruch erfolgt somit, wenn der Iterationszähler '0' erreicht hat.
Zur Realisierung der Ersetzung nach einer vorgegebenen Wahrscheinlichkeit (Erweiterung B2) habe ich schlussendlich noch die Klasse Replacement eingeführt:
Über die Funktion Turtle.GetReplacement() wird dann für jeden Aufruf eine Zufallszahl generiert und über diese in Abhängigkeit von der in Replacement hinterlegten Wahrscheinlichkeit eine zufällige Ersetzung ausgewählt.
Soweit erst mal die 'Schildkröte' an sich!
Zur Vervollständigung hier noch eine kleine Oberfläche, um das ganze zu bewundern.
PS:
Wer Interesse hat, dem schicke ich auch gern das komplette Projekt für VS 2005
Gruss Steffen
Hier kommt erstmal die Hauptklasse 'Turtle':
Code:
public class Turtle
{
private int _PictureWidth;
private int _PictureHight;
private float _StartX;
private float _StartY;
private float _StepWidth;
private float _RotationAngle;
private String _Action;
private int _Iterations;
private Dictionary<Char,List<Replacement>> _Replacements;
private int _currentindex;
private Stack<int> _Buffer;
private MultiLine currentMultiLine;
private List<MultiLine> _Lines;
public int Width { get { return _PictureWidth; } }
public int Hight { get { return _PictureHight; } }
private String _Actionlist { get { return GetActionList(_Action, _Iterations); } }
/// <summary>
/// initialisiert die "Schildkröte" mit den in Code angegebenen Werten
/// </summary>
/// <param name="code">ein Array von Strings, wobei jeder String einer Zeile im Code entspricht</param>
/// <returns>true, wenn Initialisierung erfolgreich, ansonsten false (dann stimmt irgendwas im Code nicht!)</returns>
public bool Initialize(String[] code)
{
if (code == null || code.Length == 0) return false;
try
{
// 1. Zeile: Zeichenfläche
String[] picture = code[0].Split(' ');
_PictureWidth = int.Parse(picture[0]);
_PictureHight = int.Parse(picture[1]);
// 2. Zeile: StartPosition
String[] startPos = code[1].Split(' ');
_StartX = float.Parse(startPos[0].Replace('.',','));
_StartY = float.Parse(startPos[1].Replace('.', ','));
// 3. Zeile: Schrittweite
_StepWidth = float.Parse(code[2].Replace('.', ','));
// 4. Zeile: Drehwinkel
_RotationAngle = float.Parse(code[3].Replace('.', ','));
// 5. Zeile: Aktionen
_Action = code[4];
if (code.Length > 6)
{
// 6. Zeile (falls vorhanden): Iterationszahl
_Iterations = int.Parse(code[5]);
// 7. Zeile (falls vorhanden): Ersetzungsliste bzw. Ersetzungsregeln
_Replacements = new Dictionary<char, List<Replacement>>();
for (int i = 6; i < code.Length; i++)
{
// so kann Ersetzungsliste bzw. Ersetzungsregeln aussehen
// 1) [Ersetungsliste]
// 2) [Schlüssel] [Ersetungsliste]
// 3) [Schlüssel] [Wahrscheinlichkeit] [Ersetungsliste]
String[] replacement = code[i].Split(' ');
switch (replacement.Length)
{
case 1:
AddReplacement('F', replacement[0]);
break;
case 2:
if (replacement[0].Length != 1)
throw new Exception();
AddReplacement(replacement[0][0], replacement[1]);
break;
case 3:
AddReplacement(replacement[0][0], int.Parse(replacement[1]), replacement[2]);
break;
default:
throw new Exception();
}
}
}
}
catch
{
MessageBox.Show(
"Fehler im Code!",
"Warnung",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
return false;
}
return true;
}
/// <summary>
/// führt die im Code vorgegebenen Aktionen aus
/// </summary>
/// <returns>eine Liste von zu zeichnenten "Multilines"
/// Jede "Multiline" beinhaltet im wesentlichen eine Liste von Punkten (Float-Koordinaten)
/// und die "Blickrichtung" der "Schildkröte" im letzten Punkt</returns>
public List<MultiLine> Paint()
{
currentMultiLine = new MultiLine();
_Lines = new List<MultiLine>();
_Lines.Add(currentMultiLine);
_currentindex = 0;
currentMultiLine.AddStartPoint(new PointF(_StartX, _StartY));
currentMultiLine.CurrentDirection = 0f;
foreach (Char action in _Actionlist)
{
switch (action)
{
case 'F': DrawLine(); break;
case '+': RotatePlus(); break;
case '-': RotateMinus(); break;
case '[': Push(); break;
case ']': Pop(); break;
}
}
return _Lines;
}
private void AddReplacement(char key, string value)
{
AddReplacement(key, 1, value);
}
private void AddReplacement(char key, int probability, string value)
{
if(!_Replacements.ContainsKey(key))
_Replacements[key]=new List<Replacement>();
_Replacements[key].Add(new Replacement(probability, value));
}
private void DrawLine()
{
currentMultiLine.DrawLine(_StepWidth);
}
private void RotatePlus()
{
currentMultiLine.CurrentDirection += _RotationAngle;
}
private void RotateMinus()
{
currentMultiLine.CurrentDirection -= _RotationAngle;
}
private void Push()
{
// es beginnt ein neuer Linienzug an der aktuellen Position und Richtung
// der aktuelle Linienzug wird gemerkt
MultiLine newLine = new MultiLine();
newLine.CurrentDirection = currentMultiLine.CurrentDirection;
newLine.AddStartPoint(currentMultiLine.LastPoint);
// neuer Linienzug in Liste
_Lines.Add(newLine);
// mit neuem Linienzug weiter
currentMultiLine = newLine;
// alten index auf Stack merken
if (_Buffer == null)
_Buffer = new Stack<int>();
_Buffer.Push(_currentindex);
// neuer Index ist der des eben eingefügten Linienzugs
_currentindex = _Lines.Count - 1;
}
private void Pop()
{
// wir holen den zuletzt gemerkten Linienzug
_currentindex=_Buffer.Pop();
currentMultiLine = _Lines[_currentindex];
}
/// <summary>
/// iterative Ausführung der Ersetzungen der Ausdrücke in der Startliste
/// </summary>
/// <param name="startList">String, der ggf. zu ersetzende Befehle enthält
/// (alles was nicht ersetzt werden kann, wird übernommen</param>
/// <param name="iteration">rückwärts laufender Iterationszähler</param>
/// <returns>Ergebnisliste nach erfoögter Ersetzung</returns>
private String GetActionList(String startList, int iteration)
{
// bei Iteration=0 --> startliste zurückgeben
if (iteration == 0)
return startList;
// Iteration ausführen:
// Ersetzungen in Startlist vornehmen und rekursiver Aufruf
String result = "";
foreach (Char act in startList)
{
//if (act == 'F')
// result += replacement;
//else
// result += new string(act, 1);
if (_Replacements.ContainsKey(act))
result += GetReplacement(_Replacements[act]);
else
result += new string(act, 1);
}
return GetActionList(result, iteration - 1);
}
private string GetReplacement(List<Replacement> list)
{
// Sonderfall: nur eine Ersetzung vorhanden:
// --> wahrscheinlichkeit ist 1.0
if (list.Count == 1)
return list[0].Value;
// bei mehreren Ersetzungen zufälligen Wert nach Wahrscheinlichkeit berechnen
List<String> values = new List<string>();
// jeder Wert wird [Wahrscheinlichkei]-mal in eine Liste geschrieben
// dansch wird mittels einer Zufallszahl ein Wert dieser Liste ausgewählt und zurückgegeben
foreach (Replacement repl in list)
for (int i = 0; i < repl.Probability; i++)
values.Add(repl.Value);
return values[CreateRandomNumber(values.Count)];
}
private int CreateRandomNumber(int max)
{
// das hab ich mal fix aus der Onlinehilfe von VS2005 "geklaut"
// Create a byte array to hold the random value.
byte[] randomNumber = new byte[1];
// Create a new instance of the RNGCryptoServiceProvider.
RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();
// Fill the array with a random value.
Gen.GetBytes(randomNumber);
// Convert the byte to an integer value to make the modulus operation easier.
int rand = Convert.ToInt32(randomNumber[0]);
// Return the random number mod the number
// of sides. The possible values are zero-based.
return rand % max;
}
}
Mit der Funktion Initialize() wird der 'Schildkröte' mitgeteilt, was sie alles machen soll, sprich der Code eingelesen.
Die Funktion Paint() übernimmt dann das eigentliche Zeichnen, d.h. sie erzeugt eine Liste von 'Multilines', die wie folgt definiert sind:
Code:
/// <summary>
/// Jede "Multiline" beinhaltet im wesentlichen eine Liste von Punkten (Float-Koordinaten)
/// und die "Blickrichtung" der "Schildkröte" im letzten Punkt
/// </summary>
public class MultiLine
{
public List<PointF> _Points;
public float _CurrentDirection;
public MultiLine()
{
_Points = new List<PointF>();
_CurrentDirection = 0f;
}
public List<PointF> Points { get { return _Points; } }
public float CurrentDirection
{
get { return _CurrentDirection; }
set
{
// normalisieren auf Werte zwischen 0 und 360 Grad
_CurrentDirection = value;
while (_CurrentDirection > 360)
_CurrentDirection -= 360;
while (_CurrentDirection < 0)
_CurrentDirection += 360;
}
}
public PointF LastPoint { get { return _Points[_Points.Count - 1]; } }
private double _DirectionArc { get { return (double)(_CurrentDirection * Math.PI / 180); } }
/// <summary>
/// dient hauptsächlich zum setzen des Startpunkts
/// </summary>
/// <param name="point">der Startpunkt</param>
public void AddStartPoint(PointF point)
{
_Points.Add(point);
}
/// <summary>
/// fügt neuen Punkt zur Liste hinzu, der sich durch das zeichnen einer Linie der angegebenen
/// vom letzten Punkt in der Liste in die 'CurrentDirection' ergibt
/// </summary>
/// <param name="length">die Länge der zu zeichnenten Linie</param>
internal void DrawLine(float length)
{
PointF start = _Points[_Points.Count - 1];
// Berechnung der neuen x- und y-Koordinate über Winkelfunktionen am rechtwinkligen Dreieck
float dX = length * (float)Math.Cos(_DirectionArc);
float dY = length * (float)Math.Sin(_DirectionArc);
_Points.Add(new PointF(start.X + dX, start.Y + dY));
}
}
Diese Klasse ist eigentlich nichts anderes als eine Liste von float-Koordinaten sowie einem aktuellen Blickwinkel, also der Blickrichtung der 'Schildkröte' am letzten Punkt dieser Liste.
Die Funktion AddStartPoint() fügt den Anfangspunkt in die Liste ein, während DrawLine() eine Linie mit der im Parameter übergebenen Länge ab dem letzt Punkt in die aktuelle Blickrichtung zeichnet, d.h. den Endpunkt dieser Linie in die Liste einfügt.
Die iterative Ersetzung wird durch die Funktion GetActionList() realisiert, die von der Funktion Paint() [über die Property _ActionList] aufgerufen wird. Die Iteration wird dabei durch rekursive Aufrufe von GetActionList() mit rückwärtslaufendem Iterationszähler als Parameter erreicht; Abbruch erfolgt somit, wenn der Iterationszähler '0' erreicht hat.
Zur Realisierung der Ersetzung nach einer vorgegebenen Wahrscheinlichkeit (Erweiterung B2) habe ich schlussendlich noch die Klasse Replacement eingeführt:
Code:
public class Replacement
{
private String _Replacement;
private int _Probability;
public Replacement(int probability, String value)
{
_Replacement = value;
_Probability = probability;
}
public String Value { get { return _Replacement; } }
public int Probability { get { return _Probability; } }
}
Über die Funktion Turtle.GetReplacement() wird dann für jeden Aufruf eine Zufallszahl generiert und über diese in Abhängigkeit von der in Replacement hinterlegten Wahrscheinlichkeit eine zufällige Ersetzung ausgewählt.
Soweit erst mal die 'Schildkröte' an sich!
Zur Vervollständigung hier noch eine kleine Oberfläche, um das ganze zu bewundern.
Code:
public class Form1 : Form
{
/// <summary>
/// Erforderliche Designervariable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
/// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Windows Form-Designer generierter Code
/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(12, 33);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(557, 111);
this.textBox1.TabIndex = 0;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(9, 17);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 1;
this.label1.Text = "Code:";
//
// button1
//
this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.button1.Location = new System.Drawing.Point(494, 4);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 2;
this.button1.Text = "zeichnen";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// panel1
//
this.panel1.BackColor = System.Drawing.SystemColors.ControlLightLight;
this.panel1.Location = new System.Drawing.Point(12, 150);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(557, 302);
this.panel1.TabIndex = 3;
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(581, 464);
this.Controls.Add(this.panel1);
this.Controls.Add(this.button1);
this.Controls.Add(this.label1);
this.Controls.Add(this.textBox1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Panel panel1;
private Turtle _Turtle;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
_Turtle = new Turtle();
if (_Turtle.Initialize(textBox1.Lines))
DoPaint(_Turtle.Paint());
}
private List<MultiLine> _Lines;
private void DoPaint(List<MultiLine> lines)
{
panel1.Width = _Turtle.Width;
panel1.Height = _Turtle.Hight;
_Lines=lines;
panel1.Invalidate();
panel1.Update();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
if (_Lines == null || _Lines.Count == 0) return;
// Create Pen
Pen pen = new Pen(Color.Black, 1);
// die einzelnen Linien zeichnen
int i = -1;
foreach (MultiLine line in _Lines)
{
i++;
// zum zeichnen brauchen wir mindestens 2 Punkte
if (line.Points.Count < 2)
continue;
// Punkte-Array erzeugen
PointF[] points = ConvertPoints(line.Points);
// linien zeichnen
try
{
e.Graphics.DrawLines(pen, points);
}
catch (Exception ex)
{
String msg = ex.Message;
}
}
}
private PointF[] ConvertPoints(List<PointF> list)
{
PointF[] points = new PointF[list.Count];
for (int i = 0; i < list.Count; i++)
{
PointF pt = list[i];
pt.Y = panel1.Height - pt.Y;
points[i] = pt;
}
return points;
}
}
PS:
Wer Interesse hat, dem schicke ich auch gern das komplette Projekt für VS 2005
Gruss Steffen