You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
4.3 KiB
201 lines
4.3 KiB
using System; |
|
using Cairo; |
|
using System.Text.RegularExpressions; |
|
using System.Resources; |
|
using System.Collections.Generic; |
|
|
|
namespace SBRL.SimpleTurtle |
|
{ |
|
public class TurtleState : ICloneable { |
|
public PointD Position; |
|
public double Heading; |
|
|
|
public TurtleState(PointD inPosition, double inHeading) { |
|
Position = inPosition; |
|
Heading = inHeading; |
|
} |
|
|
|
public object Clone() { |
|
return new TurtleState(Position, Heading); |
|
} |
|
} |
|
|
|
public class Turtle |
|
{ |
|
public bool StrictMode = false; |
|
private string commandQueue; |
|
|
|
private readonly Stack<TurtleState> states = new Stack<TurtleState>(); |
|
public PointD Position { |
|
get => states.Peek().Position; |
|
protected set => states.Peek().Position = value; |
|
} |
|
public double Heading { |
|
get => states.Peek().Heading; |
|
protected set => states.Peek().Heading = value; |
|
} |
|
private Area bounds; |
|
|
|
public double HeadingStep { get; set; } |
|
public double MovementStep { get; set; } |
|
|
|
public Turtle () { |
|
Reset(); |
|
} |
|
|
|
public void ApplyDefinitions(Dictionary<string, string> definitions) |
|
{ |
|
foreach(KeyValuePair<string, string> definition in definitions) |
|
{ |
|
switch(definition.Key.ToLower()) |
|
{ |
|
case "angle": |
|
HeadingStep = double.Parse(definition.Value); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
public bool Commands(string commandText) |
|
{ |
|
// Remove all whitespace |
|
commandText = Regex.Replace(commandText, @"\s+", ""); |
|
commandText = commandText.Replace("h", "f"); |
|
|
|
string okCommands = "f+-"; |
|
|
|
foreach(char ch in commandText) |
|
{ |
|
if(okCommands.Contains(ch.ToString())) |
|
{ |
|
switch(ch) |
|
{ |
|
case 'f': |
|
Forwards(); |
|
break; |
|
case '+': |
|
Turn(false); |
|
break; |
|
case '-': |
|
Turn(true); |
|
break; |
|
case '[': |
|
Save(); |
|
break; |
|
case ']': |
|
Restore(); |
|
break; |
|
default: |
|
if (StrictMode) { |
|
Console.WriteLine("The unexpected character '{0}' slipped through the net!", ch); |
|
return false; |
|
} |
|
break; |
|
} |
|
} |
|
else if(StrictMode) |
|
{ |
|
Console.Error.WriteLine("Error: unexpected character '{0}'", ch); |
|
return false; |
|
} |
|
} |
|
commandQueue += commandText; |
|
return true; |
|
} |
|
|
|
public void Draw(string filename, bool reset = true) |
|
{ |
|
ImageSurface canvas = new ImageSurface(Format.ARGB32, (int)Math.Ceiling(bounds.Width + 10), (int)Math.Ceiling(bounds.Height + 10)); |
|
Context context = new Context(canvas); |
|
Position = new PointD(-bounds.X + 5, -bounds.Y + 5); |
|
Heading = 0; |
|
|
|
context.LineWidth = 3; |
|
context.MoveTo(Position); |
|
foreach(char ch in commandQueue) |
|
{ |
|
switch(ch) |
|
{ |
|
case 'f': |
|
PointD newPosition = new PointD( |
|
Position.X + MovementStep * Math.Sin(Heading), |
|
Position.Y + MovementStep * Math.Cos(Heading) |
|
); |
|
context.LineTo(newPosition); |
|
Position = newPosition; |
|
break; |
|
case '+': |
|
Heading += HeadingStep; |
|
break; |
|
case '-': |
|
Heading -= HeadingStep; |
|
break; |
|
} |
|
} |
|
|
|
context.Stroke(); |
|
canvas.WriteToPng(string.Format(filename)); |
|
context.Dispose(); |
|
canvas.Dispose(); |
|
if(reset) |
|
Reset(); |
|
} |
|
|
|
public void Forwards() |
|
{ |
|
PointD newPosition = new PointD( |
|
Position.X + MovementStep * Math.Sin(Heading), |
|
Position.Y + MovementStep * Math.Cos(Heading) |
|
); |
|
|
|
if (newPosition.X > bounds.X + bounds.Width) |
|
bounds.Width += newPosition.X - Position.X; |
|
if (newPosition.Y > bounds.Y + bounds.Height) |
|
bounds.Height += newPosition.Y - Position.Y; |
|
if (newPosition.X < bounds.X) |
|
{ |
|
bounds.X = newPosition.X; |
|
bounds.Width += Position.X - newPosition.X; |
|
} |
|
if (newPosition.Y < bounds.Y) |
|
{ |
|
bounds.Y = newPosition.Y; |
|
bounds.Height += Position.Y - newPosition.Y; |
|
} |
|
|
|
Position = newPosition; |
|
} |
|
|
|
public void Turn(bool anticlockwise = false) |
|
{ |
|
if (!anticlockwise) |
|
Heading += HeadingStep; |
|
else |
|
Heading -= HeadingStep; |
|
} |
|
|
|
public void Save() |
|
{ |
|
states.Push(states.Peek().Clone() as TurtleState); |
|
} |
|
public bool Restore() { |
|
if (states.Count <= 1) |
|
return false; |
|
|
|
states.Pop(); |
|
return true; |
|
} |
|
|
|
public void Reset() |
|
{ |
|
states.Clear(); |
|
states.Push(new TurtleState(new PointD(0, 0), 0)); |
|
|
|
commandQueue = string.Empty; |
|
bounds = new Area(Position.X, Position.Y, 1, 1); |
|
HeadingStep = Math.PI / 2; |
|
MovementStep = 25; |
|
} |
|
} |
|
} |
|
|
|
|