Gosper-kromme

In ‘Hackers –Heroes of the Computer Revolution (Steven Levy) wordt het ontstaan van de hacker gemeenschap op MIT uitvoerig beschreven. Centraal figuur is Bill Gosper. Bekend als ontdekker van oa de Gosper Glider gun (Game of Life), in 1973 de Gosper kromme en in 1985 het berekenen van 17 miljoen decimalen van pi.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace GosperViewer
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new GosperForm());
        }
    }

    public class GosperForm : Form
    {
        // View state
        private float zoom = 1.0f;
        private PointF pan = new PointF(0, 0);
        private Point lastMouse;
        private bool panning = false;

        private const float MinZoom = 0.0001f;

        // Gosper settings
        private int iteration = 4;
        private List<PointF> points;

        // UI resources (één keer aanmaken!)
        private readonly Font overlayFont = new Font("Consolas", 10);

        public GosperForm()
        {


            Text = "Gosper Curve";
            DoubleBuffered = true;
            ResizeRedraw = true;
            BackColor = Color.Black;
            WindowState = FormWindowState.Maximized;

            GenerateCurve();

            MouseWheel += OnMouseWheel;
            MouseDown += OnMouseDown;
            MouseUp += OnMouseUp;
            MouseMove += OnMouseMove;
            KeyDown += OnKeyDown;

            MouseEnter += (s, e) => Focus();
        }

        #region Background (crash prevention)

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // WinForms background painting uitschakelen
        }

        #endregion

        #region Input

        private void OnMouseWheel(object sender, MouseEventArgs e)
        {
            float oldZoom = zoom;

            zoom *= e.Delta > 0 ? 1.1f : 0.9f;
            zoom = Math.Max(zoom, MinZoom);

            float mx = e.X - Width / 2f - pan.X;
            float my = e.Y - Height / 2f - pan.Y;

            pan.X -= mx * (zoom / oldZoom - 1);
            pan.Y -= my * (zoom / oldZoom - 1);

            Invalidate();
        }

        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                panning = true;
                lastMouse = e.Location;
            }
        }

        private void OnMouseUp(object sender, MouseEventArgs e)
        {
            panning = false;
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (!panning) return;

            pan.X += e.X - lastMouse.X;
            pan.Y += e.Y - lastMouse.Y;
            lastMouse = e.Location;

            Invalidate();
        }

        private void OnKeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Add || e.KeyCode == Keys.Oemplus)
            {
                iteration = Math.Min(iteration + 1, 6);
                GenerateCurve();
            }
            else if (e.KeyCode == Keys.Subtract || e.KeyCode == Keys.OemMinus)
            {
                iteration = Math.Max(iteration - 1, 0);
                GenerateCurve();
            }
        }

        #endregion

        #region Rendering

        protected override void OnPaint(PaintEventArgs e)
        {
            if (ClientSize.Width <= 0 || ClientSize.Height <= 0)
                return;


            // Amber (klassieke monochrome terminalkleur)
            Color Amber = Color.FromArgb(255, 255, 191, 0);

        Graphics g = e.Graphics;

            g.Clear(BackColor);

            if (points == null || points.Count < 2)
                return;

            g.SmoothingMode = SmoothingMode.AntiAlias;

            // World transform
            g.TranslateTransform(
                Width / 2f + pan.X,
                Height / 2f + pan.Y
            );
            g.ScaleTransform(zoom, zoom);

            using (Pen pen = new Pen(Amber, Math.Max(1f / zoom, 0.1f)))
            {
                for (int i = 1; i < points.Count; i++)
                    g.DrawLine(pen, points[i - 1], points[i]);
            }

            g.ResetTransform();

            DrawOverlay(g);
        }

        private void DrawOverlay(Graphics g)
        {
            string text =
                $"Iteratie: {iteration}\r\n" +
                $"Punten: {points.Count}\r\n" +
                $"Zoom: {zoom:0.00}\r\n\r\n" +
                "+ / - : iteraties\r\n" +
                "Muiswiel : zoom\r\n" +
                "Slepen : pan";

            Color Amber = Color.FromArgb(255, 255, 191, 0);

            TextRenderer.DrawText(
                           g,                // owner
                           text,                // tekst
                           overlayFont,         // font
                           new Point(10, 10),   // positie
                           Amber,         // tekstkleur
                           Color.Transparent,  // achtergrond
                           TextFormatFlags.Left |
                           TextFormatFlags.Top |
                           TextFormatFlags.NoPadding
                        );
        }

        #endregion

        #region Gosper Curve

        private void GenerateCurve()
        {
            string result = "A";

            for (int i = 0; i < iteration; i++)
                result = Rewrite(result);

            points = Interpret(result);
            Invalidate();
        }

        private string Rewrite(string input)
        {
            var sb = new System.Text.StringBuilder();

            foreach (char c in input)
            {
                if (c == 'A')
                    sb.Append("A-B--B+A++AA+B-");
                else if (c == 'B')
                    sb.Append("+A-BB--B-A++A+B");
                else
                    sb.Append(c);
            }

            return sb.ToString();
        }

        private List<PointF> Interpret(string commands)
        {
            var pts = new List<PointF>();

            float angle = 0f;
            float step = 10f;
            float x = 0, y = 0;

            pts.Add(new PointF(x, y));

            foreach (char c in commands)
            {
                switch (c)
                {
                    case '+': angle += 60f; break;
                    case '-': angle -= 60f; break;
                    case 'A':
                    case 'B':
                        float rad = angle * (float)Math.PI / 180f;
                        x += step * (float)Math.Cos(rad);
                        y += step * (float)Math.Sin(rad);
                        pts.Add(new PointF(x, y));
                        break;
                }
            }

            CenterPoints(pts);
            return pts;
        }

        private void CenterPoints(List<PointF> pts)
        {
            float minX = float.MaxValue, minY = float.MaxValue;
            float maxX = float.MinValue, maxY = float.MinValue;

            foreach (var p in pts)
            {
                minX = Math.Min(minX, p.X);
                minY = Math.Min(minY, p.Y);
                maxX = Math.Max(maxX, p.X);
                maxY = Math.Max(maxY, p.Y);
            }

            float cx = (minX + maxX) / 2f;
            float cy = (minY + maxY) / 2f;

            for (int i = 0; i < pts.Count; i++)
                pts[i] = new PointF(pts[i].X - cx, pts[i].Y - cy);
        }

        #endregion
    }
}
Previous post Spacewar!