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