[QUIZ#9] SteffenBoerner (C#)

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':
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
 
Da die Veröffentlichung wohl doch etwas länger dauert, hab ich meine Zeichnungen jetzt noch ein bisschen bunter gemacht.
Im Prinzip funktioniert das mit einer eigenen Color-Klasse, die folgendermaßen aussieht:

Code:
	public class MyColor
	{
		/// <summary>
		/// Die Farbkomponenten R,G und B
		/// </summary>
		private static int[] _Components;
		/// <summary>
		/// Index der Startkomponent für Änderungen
		/// </summary>
		private static int _ComponentStart;

		public static void Init()
		{
			// Startfarbe Rot
			_Components = new int[] { 255, 0, 0 };
			// zuerst Änderungen von Rot zu Grün
			_ComponentStart = 0;
		}

		public static Color GetNextColor()
		{
			UpdateComponents();
			return Color.FromArgb(_Components[0], _Components[1], _Components[2]);
		}

		private static void UpdateComponents()
		{
			// solange die Startfarbe 255 ist, läuft die dem Startindex folgende Farbe nach oben...
			if (_Components[_ComponentStart] == 255 &&
				_Components[(_ComponentStart + 1) % 3] < 255 )
			{
				_Components[(_ComponentStart + 1) % 3]++;
				return;
			}
			// hat diesw 255 erreicht, läuft die Farbe mit dem Startindex nach unten...
			if (_Components[(_ComponentStart + 1) % 3] == 255)
				_Components[_ComponentStart]--;
			// hat diese 0 erreicht, wird der Startindex um 1 erhöht
			if (_Components[_ComponentStart] == 0)
				_ComponentStart = (++_ComponentStart) % 3;
		}
	}

Die Werte und Methoden sollten durch die Kommentare ausreichend beschrieben sein.

Jetzt brauch ich beim Zeichnen meiner MultiLines, die jetzt von Punkt für Punkt gezeichnet werden, nur noch für jede zu zeichnenden Einzellinie eine neue Farbe holen. Somit sieht die Methode panel1_Paint, die die Ausgabe der Multilines bewerkstelligt, jetzt wie folgt aus:


Code:
		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);

				// jede MultiLine in anderer Farbe zeichnen
				//pen.Color = MyColor.GetNextColor();

				// linien zeichnen
				try
				{
					//e.Graphics.DrawLines(pen, points);
					//// jede Teilline der Multiline erhält neue Farbe
					pen = new Pen(Color.Black, 1);
					for (int p = 0; p < points.Length - 1; p++)
					{
						pen.Color = MyColor.GetNextColor();
						e.Graphics.DrawLine(pen, points[p], points[p + 1]);
					}
				}
				catch (Exception ex)
				{
					String msg = ex.Message;
				}
			}
		}

Das geänderte Gesamtprojet auch hier wieder als Anhang!

Gruß Steffen
 

Anhänge

  • TurtleGrafik.zip
    745,5 KB · Aufrufe: 20
Hi.

Schön, dass bei den Quiz-Abgaben immer auch C# vertretten ist. :)

Zu deiner Lösung kurz etwas, das mir aufgefallen ist.

C#:
_StartX = float.Parse(startPos[0].Replace('.',','));

Hier könntest du mit einer Überlagerung von Parse arbeiten:

C#:
using System.Globalization;
// ...
_StartX = float.Parse(startPos[0], CultureInfo.InvariantCulture

Ist wie ich finde die schönere Lösung,.. :)

lg, Alex
 
Zurück