2018-01-28 23:30:54 +00:00
|
|
|
|
using System;
|
|
|
|
|
using Cairo;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Resources;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
namespace SBRL.SimpleTurtle
|
|
|
|
|
{
|
2018-11-04 13:55:27 +00:00
|
|
|
|
public class TurtleState : ICloneable {
|
|
|
|
|
public PointD Position;
|
|
|
|
|
public double Heading;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
|
2018-11-04 13:55:27 +00:00
|
|
|
|
public TurtleState(PointD inPosition, double inHeading) {
|
|
|
|
|
Position = inPosition;
|
|
|
|
|
Heading = inHeading;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public object Clone() {
|
|
|
|
|
return new TurtleState(Position, Heading);
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-04 13:55:27 +00:00
|
|
|
|
|
2018-01-28 23:30:54 +00:00
|
|
|
|
public class Turtle
|
|
|
|
|
{
|
2018-11-04 13:55:27 +00:00
|
|
|
|
public bool StrictMode = false;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
private string commandQueue;
|
|
|
|
|
|
2018-11-04 13:55:27 +00:00
|
|
|
|
private readonly Stack<TurtleState> states = new Stack<TurtleState>();
|
|
|
|
|
public PointD Position {
|
|
|
|
|
get => states.Peek().Position;
|
|
|
|
|
protected set => states.Peek().Position = value;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
2018-11-04 13:55:27 +00:00
|
|
|
|
public double Heading {
|
|
|
|
|
get => states.Peek().Heading;
|
|
|
|
|
protected set => states.Peek().Heading = value;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
2018-11-04 13:55:27 +00:00
|
|
|
|
private Area bounds;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
|
2018-11-04 13:55:27 +00:00
|
|
|
|
public double HeadingStep { get; set; }
|
|
|
|
|
public double MovementStep { get; set; }
|
|
|
|
|
|
|
|
|
|
public Turtle () {
|
2018-01-28 23:30:54 +00:00
|
|
|
|
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;
|
2018-11-04 13:55:27 +00:00
|
|
|
|
case '[':
|
|
|
|
|
Save();
|
|
|
|
|
break;
|
|
|
|
|
case ']':
|
|
|
|
|
Restore();
|
|
|
|
|
break;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
default:
|
2018-11-04 13:55:27 +00:00
|
|
|
|
if (StrictMode) {
|
2018-01-28 23:30:54 +00:00
|
|
|
|
Console.WriteLine("The unexpected character '{0}' slipped through the net!", ch);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-04 13:55:27 +00:00
|
|
|
|
else if(StrictMode)
|
2018-01-28 23:30:54 +00:00
|
|
|
|
{
|
|
|
|
|
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);
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Position = new PointD(-bounds.X + 5, -bounds.Y + 5);
|
|
|
|
|
Heading = 0;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
|
|
|
|
|
context.LineWidth = 3;
|
2018-11-04 13:55:27 +00:00
|
|
|
|
context.MoveTo(Position);
|
2018-01-28 23:30:54 +00:00
|
|
|
|
foreach(char ch in commandQueue)
|
|
|
|
|
{
|
|
|
|
|
switch(ch)
|
|
|
|
|
{
|
|
|
|
|
case 'f':
|
|
|
|
|
PointD newPosition = new PointD(
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Position.X + MovementStep * Math.Sin(Heading),
|
|
|
|
|
Position.Y + MovementStep * Math.Cos(Heading)
|
2018-01-28 23:30:54 +00:00
|
|
|
|
);
|
|
|
|
|
context.LineTo(newPosition);
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Position = newPosition;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
break;
|
|
|
|
|
case '+':
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Heading += HeadingStep;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
break;
|
|
|
|
|
case '-':
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Heading -= HeadingStep;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.Stroke();
|
|
|
|
|
canvas.WriteToPng(string.Format(filename));
|
|
|
|
|
context.Dispose();
|
|
|
|
|
canvas.Dispose();
|
|
|
|
|
if(reset)
|
|
|
|
|
Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Forwards()
|
|
|
|
|
{
|
|
|
|
|
PointD newPosition = new PointD(
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Position.X + MovementStep * Math.Sin(Heading),
|
|
|
|
|
Position.Y + MovementStep * Math.Cos(Heading)
|
2018-01-28 23:30:54 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (newPosition.X > bounds.X + bounds.Width)
|
2018-11-04 13:55:27 +00:00
|
|
|
|
bounds.Width += newPosition.X - Position.X;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
if (newPosition.Y > bounds.Y + bounds.Height)
|
2018-11-04 13:55:27 +00:00
|
|
|
|
bounds.Height += newPosition.Y - Position.Y;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
if (newPosition.X < bounds.X)
|
|
|
|
|
{
|
|
|
|
|
bounds.X = newPosition.X;
|
2018-11-04 13:55:27 +00:00
|
|
|
|
bounds.Width += Position.X - newPosition.X;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
|
|
|
|
if (newPosition.Y < bounds.Y)
|
|
|
|
|
{
|
|
|
|
|
bounds.Y = newPosition.Y;
|
2018-11-04 13:55:27 +00:00
|
|
|
|
bounds.Height += Position.Y - newPosition.Y;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Position = newPosition;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Turn(bool anticlockwise = false)
|
|
|
|
|
{
|
|
|
|
|
if (!anticlockwise)
|
2018-11-04 13:55:27 +00:00
|
|
|
|
Heading += HeadingStep;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
else
|
2018-11-04 13:55:27 +00:00
|
|
|
|
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;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Reset()
|
|
|
|
|
{
|
2018-11-04 13:55:27 +00:00
|
|
|
|
states.Clear();
|
|
|
|
|
states.Push(new TurtleState(new PointD(0, 0), 0));
|
|
|
|
|
|
2018-01-28 23:30:54 +00:00
|
|
|
|
commandQueue = string.Empty;
|
2018-11-04 13:55:27 +00:00
|
|
|
|
bounds = new Area(Position.X, Position.Y, 1, 1);
|
|
|
|
|
HeadingStep = Math.PI / 2;
|
|
|
|
|
MovementStep = 25;
|
2018-01-28 23:30:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|