randomfox (randomfox) wrote,
randomfox
randomfox

Hopalong Strange Attractor in C#

This 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.


screenshot

// 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

Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments