Hopalong Strange Attractor in C#
Apr. 1st, 2005 05:30 pmThis is an example of a strange attractor, a formula that generates each subsequent point from the previous one and produces intriguing and surprising patterns when plotted on screen. Different values of P, Q, and R will produce different patterns.

// hopalong - Produces graphics using the hopalong attractor.
//
// Author: Po Shan Cheah
// Last updated: August 22, 2003
//
// Compile with: csc /doc:hopalong.xml hopalong.cs
namespace HopAlong {
using System;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
/// <summary>Contains code that runs the generator function. Also manages
/// the thread and timer for the generator and the display
/// refresh.</summary>
class HopAlongGenerator {
/// <summary>Change brush color every this number of
/// iterations.</summary>
const int ColorChangeIterations = 500;
/// <summary>Number of milliseconds between display updates.</summary>
const int RefreshInterval = 50;
double p;
double q;
double r;
int canvasWidth;
int canvasHeight;
// The offscreen buffer.
Bitmap image;
public HopAlongGenerator(double p, double q, double r,
int canvasWidth, int canvasHeight) {
this.p = p;
this.q = q;
this.r = r;
// The width and height of the drawing area is saved just once,
// when the Go button is clicked on. Any resizing after that won't
// change the drawing area until the generator is restarted.
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
}
public void Run() {
double multiplier = 10 / Math.Sqrt(2);
// Offscreen buffer.
image = new Bitmap(canvasWidth, canvasHeight);
Graphics graphics = Graphics.FromImage(image);
int xmid = canvasWidth / 2;
int ymid = canvasHeight / 2;
graphics.Clear(Color.Black);
int count = 0;
double x = 0;
double y = 0;
SolidBrush brush = new SolidBrush(Color.White);
Random rand = new Random();
while (true) {
// Change the brush color every ColorChangeIterations iterations.
if (count % ColorChangeIterations == 0) {
brush.Dispose();
brush = new SolidBrush(Color.FromArgb(rand.Next(128, 256),
rand.Next(128, 256),
rand.Next(128, 256)));
}
int sign = x < 0 ? -1 : 1;
double x1 = y - sign * Math.Sqrt(Math.Abs(q * x - r));
y = p - x;
x = x1;
// Plot a point.
graphics.FillRectangle(brush,
(int) (Math.Round((x + y) *
multiplier) + xmid),
(int) (Math.Round((y - x) *
multiplier) + ymid),
1, 1);
++count;
}
} // Run
/// <value>The generated image.</value>
public Bitmap Image {
get { return image; }
}
Thread runThread;
System.Windows.Forms.Timer refreshTimer;
/// <summary>Start the generator thread and display refresh
/// timer.</summary>
/// <param name="refreshFunc">Function that will be called every
/// RefreshInterval milliseconds.</param>
public void Start(EventHandler refreshFunc) {
runThread = new Thread(new ThreadStart(Run));
runThread.Start();
runThread.IsBackground = true;
if (refreshTimer == null) {
refreshTimer = new System.Windows.Forms.Timer();
refreshTimer.Tick += refreshFunc;
refreshTimer.Interval = RefreshInterval;
refreshTimer.Start();
}
} // Start
/// <summary>Stop the generator thread and display refresh
/// timer.</summary>
public void Stop() {
if (runThread != null) {
runThread.Abort();
runThread = null;
}
if (refreshTimer != null) {
refreshTimer.Stop();
refreshTimer.Dispose();
refreshTimer = null;
}
} // Stop
} // class HopAlongGenerator
class HopAlong : Form {
const int TopMargin = 30;
TextBox pfield;
TextBox qfield;
TextBox rfield;
SolidBrush blackBrush = new SolidBrush(Color.Black);
HopAlongGenerator hopalong;
/// <summary>Paint event handler.</summary>
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.FillRectangle(blackBrush, 0, TopMargin,
ClientRectangle.Width,
ClientRectangle.Height - TopMargin);
if (hopalong != null && hopalong.Image != null)
e.Graphics.DrawImage(hopalong.Image, 0, TopMargin);
}
/// <summary>Timer event handler. Will be called periodically to redraw
/// the image.</summary>
void Tick(Object o, EventArgs e) {
Invalidate();
// Invalidate() by itself does not trigger a repaint.
Update();
}
/// <summary>Event handler for the Go button.</summary>
void go_Click(object o, EventArgs e) {
double p;
double q;
double r;
try {
p = double.Parse(pfield.Text);
q = double.Parse(qfield.Text);
r = double.Parse(rfield.Text);
}
catch (FormatException) {
MessageBox.Show("Invalid numeric value entered.");
return;
}
// If there is a generator running, we have to stop it before
// starting another one.
if (hopalong != null)
hopalong.Stop();
hopalong = new HopAlongGenerator(p, q, r,
ClientRectangle.Width,
ClientRectangle.Height - TopMargin);
hopalong.Start(new EventHandler(Tick));
} // go_Click
/// <summary>Event handler for the Stop button.</summary>
void stop_Click(object o, EventArgs e) {
hopalong.Stop();
} // stop_Click
/// <summary>Event handler for the Random button.</summary>
void random_Click(object o, EventArgs e) {
Random rand = new Random();
pfield.Text = (rand.NextDouble() + 0.3).ToString("#.####");
qfield.Text = (rand.NextDouble() + 0.3).ToString("#.####");
rfield.Text = (rand.NextDouble() + 0.3).ToString("#.####");
} // random_Click
HopAlong() {
Text = "HopAlong";
Name = "HopAlong";
// Activate double-buffering.
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
ClientSize = new Size(700, 600);
Label l1 = new Label();
l1.Text = "Try values between 0 and 2. P:";
l1.TextAlign = ContentAlignment.MiddleRight;
l1.Location = new Point(0, 0);
l1.Size = new Size(160, 25);
Controls.Add(l1);
pfield = new TextBox();
pfield.Text = "0.5";
pfield.MaxLength = 8;
pfield.Location = new Point(160, 3);
pfield.Size = new Size(75, 25);
Controls.Add(pfield);
Label l2 = new Label();
l2.Text = "Q:";
l2.TextAlign = ContentAlignment.MiddleRight;
l2.Location = new Point(235, 0);
l2.Size = new Size(30, 25);
Controls.Add(l2);
qfield = new TextBox();
qfield.Text = "0.5";
qfield.MaxLength = 8;
qfield.Location = new Point(265, 3);
qfield.Size = new Size(75, 25);
Controls.Add(qfield);
Label l3 = new Label();
l3.Text = "R:";
l3.TextAlign = ContentAlignment.MiddleRight;
l3.Location = new Point(340, 0);
l3.Size = new Size(30, 25);
Controls.Add(l3);
rfield = new TextBox();
rfield.Text = "0.5";
rfield.MaxLength = 8;
rfield.Location = new Point(370, 3);
rfield.Size = new Size(75, 25);
Controls.Add(rfield);
Button goButton = new Button();
goButton.Text = "Go";
goButton.Location = new Point(455, 3);
goButton.Size = new Size(50, 20);
goButton.Click += new EventHandler(go_Click);
Controls.Add(goButton);
AcceptButton = goButton;
Button randomButton = new Button();
randomButton.Text = "Random";
randomButton.Location = new Point(515, 3);
randomButton.Size = new Size(70, 20);
randomButton.Click += new EventHandler(random_Click);
Controls.Add(randomButton);
Button stopButton = new Button();
stopButton.Text = "Stop";
stopButton.Location = new Point(595, 3);
stopButton.Size = new Size(50, 20);
stopButton.Click += new EventHandler(stop_Click);
Controls.Add(stopButton);
}
static int Main() {
Application.Run(new HopAlong());
return 0;
}
} // class HopAlong
} // namespace HopAlong
// The End