Model: anthropic/claude-opus-4-5-20251101
Build me a mandelbrot fractal CLI tool in this language
https://raw.githubusercontent.com/jordanhubbard/nanolang/refs/heads/main/MEMORY.md
I'll build a complete Mandelbrot fractal generator based on the Nano language specification.
#!/usr/bin/env nano
# Mandelbrot Fractal CLI Tool
# Generates ASCII art Mandelbrot fractals
# ============================================================================
# Configuration and Constants
# ============================================================================
# Default rendering parameters
let DEFAULT_WIDTH = 80
let DEFAULT_HEIGHT = 40
let DEFAULT_MAX_ITER = 100
# Default view bounds (classic Mandelbrot view)
let DEFAULT_X_MIN = -2.5
let DEFAULT_X_MAX = 1.0
let DEFAULT_Y_MIN = -1.0
let DEFAULT_Y_MAX = 1.0
# ASCII gradient characters (from darkest to brightest)
let ASCII_GRADIENT = " .:-=+*#%@"
let BLOCK_GRADIENT = " ░▒▓█"
# ============================================================================
# Complex Number Operations (using tables as complex numbers)
# ============================================================================
# Create a complex number
fn complex(real, imag) {
{ re: real, im: imag }
}
# Add two complex numbers
fn cadd(a, b) {
complex(a.re + b.re, a.im + b.im)
}
# Multiply two complex numbers: (a+bi)(c+di) = (ac-bd) + (ad+bc)i
fn cmul(a, b) {
let real = a.re * b.re - a.im * b.im
let imag = a.re * b.im + a.im * b.re
complex(real, imag)
}
# Square of absolute value: |z|^2 = re^2 + im^2
fn cabs_sq(z) {
z.re * z.re + z.im * z.im
}
# ============================================================================
# Mandelbrot Iteration
# ============================================================================
# Calculate escape iteration for a point
# Returns iteration count (0 to max_iter)
# z_{n+1} = z_n^2 + c, starting with z_0 = 0
fn mandelbrot_iter(c, max_iter) {
var z = complex(0.0, 0.0)
var iter = 0
while iter < max_iter and cabs_sq(z) <= 4.0 {
# z = z^2 + c
z = cadd(cmul(z, z), c)
iter = iter + 1
}
iter
}
# Smooth iteration count for better coloring
fn mandelbrot_smooth(c, max_iter) {
var z = complex(0.0, 0.0)
var iter = 0
while iter < max_iter and cabs_sq(z) <= 256.0 {
z = cadd(cmul(z, z), c)
iter = iter + 1
}
if iter == max_iter {
max_iter
} else {
# Smooth coloring formula
let log_zn = log(cabs_sq(z)) / 2.0
let nu = log(log_zn / log(2.0)) / log(2.0)
iter + 1 - nu
}
}
# ============================================================================
# Rendering Functions
# ============================================================================
# Map iteration count to ASCII character
fn iter_to_char(iter, max_iter, gradient) {
if iter >= max_iter {
# Point is in the set - use space (black)
" "
} else {
let gradient_len = len(gradient)
let index = floor((iter / max_iter) * (gradient_len - 1))
# Clamp index to valid range
let safe_index = if index < 0 { 0 }
else if index >= gradient_len { gradient_len - 1 }
else { index }
slice(gradient, safe_index, safe_index + 1)
}
}
# Map pixel coordinates to complex plane
fn pixel_to_complex(px, py, width, height, bounds) {
let x = bounds.x_min + (px / width) * (bounds.x_max - bounds.x_min)
let y = bounds.y_min + (py / height) * (bounds.y_max - bounds.y_min)
complex(x, y)
}
# Render the Mandelbrot set to a string
fn render_mandelbrot(config) {
let width = config.width or DEFAULT_WIDTH
let height = config.height or DEFAULT_HEIGHT
let max_iter = config.max_iter or DEFAULT_MAX_ITER
let gradient = config.gradient or ASCII_GRADIENT
let bounds = {
x_min: config.x_min or DEFAULT_X_MIN,
x_max: config.x_max or DEFAULT_X_MAX,
y_min: config.y_min or DEFAULT_Y_MIN,
y_max: config.y_max or DEFAULT_Y_MAX
}
var output = ""
var y = 0
while y < height {
var x = 0
var line = ""
while x < width {
let c = pixel_to_complex(x, y, width, height, bounds)
let iter = mandelbrot_iter(c, max_iter)
let char = iter_to_char(iter, max_iter, gradient)
line = line ++ char
x = x + 1
}
output = output ++ line ++ "\n"
y = y + 1
}
output
}
# ============================================================================
# Preset Views
# ============================================================================
let PRESETS = {
full: {
name: "Full Mandelbrot",
x_min: -2.5, x_max: 1.0,
y_min: -1.0, y_max: 1.0
},
seahorse: {
name: "Seahorse Valley",
x_min: -0.8, x_max: -0.7,
y_min: -0.15, y_max: 0.05
},
elephant: {
name: "Elephant Valley",
x_min: 0.2, x_max: 0.4,
y_min: -0.1, y_max: 0.1
},
spiral: {
name: "Spiral Detail",
x_min: -0.38, x_max: -0.34,
y_min: 0.64, y_max: 0.68
},
lightning: {
name: "Lightning",
x_min: -1.48, x_max: -1.44,
y_min: -0.02, y_max: 0.02
},
julia_island: {
name: "Julia Island",
x_min: -1.77, x_max: -1.74,
y_min: -0.02, y_max: 0.01
},
mini: {
name: "Mini Mandelbrot",
x_min: -0.105, x_max: -0.085,
y_min: 0.925, y_max: 0.945
}
}
# ============================================================================
# CLI Argument Parsing
# ============================================================================
fn parse_args(args) {
var config = {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
max_iter: DEFAULT_MAX_ITER,
gradient: ASCII_GRADIENT,
x_min: DEFAULT_X_MIN,
x_max: DEFAULT_X_MAX,
y_min: DEFAULT_Y_MIN,
y_max: DEFAULT_Y_MAX,
help: false,
list_presets: false,
animate: false,
zoom_target: nil
}
var i = 0
while i < len(args) {
let arg = args[i]
if arg == "-h" or arg == "--help" {
config.help = true
} else if arg == "-w" or arg == "--width" {
i = i + 1
config.width = int(args[i])
} else if arg == "-H" or arg == "--height" {
i = i + 1
config.height = int(args[i])
} else if arg == "-i" or arg == "--iterations" {
i = i + 1
config.max_iter = int(args[i])
} else if arg == "-p" or arg == "--preset" {
i = i + 1
let preset_name = args[i]
if has(PRESETS, preset_name) {
let preset = PRESETS[preset_name]
config.x_min = preset.x_min
config.x_max = preset.x_max
config.y_min = preset.y_min
config.y_max = preset.y_max
} else {
print("Unknown preset: " ++ preset_name)
config.list_presets = true
}
} else if arg == "-l" or arg == "--list" {
config.list_presets = true
} else if arg == "-g" or arg == "--gradient" {
i = i + 1
let grad_name = args[i]
if grad_name == "block" {
config.gradient = BLOCK_GRADIENT
} else if grad_name == "ascii" {
config.gradient = ASCII_GRADIENT
} else {
# Custom gradient string
config.gradient = grad_name
}
} else if arg == "-x" or arg == "--x-min" {
i = i + 1
config.x_min = float(args[i])
} else if arg == "-X" or arg == "--x-max" {
i = i + 1
config.x_max = float(args[i])
} else if arg == "-y" or arg == "--y-min" {
i = i + 1
config.y_min = float(args[i])
} else if arg == "-Y" or arg == "--y-max" {
i = i + 1
config.y_max = float(args[i])
} else if arg == "-a" or arg == "--animate" {
config.animate = true
} else if arg == "-z" or arg == "--zoom" {
i = i + 1
config.zoom_target = args[i]
}
i = i + 1
}
config
}
# ============================================================================
# Help and Info Display
# ============================================================================
fn show_help() {
print("
╔═══════════════════════════════════════════════════════════════════════════╗
║ MANDELBROT FRACTAL GENERATOR ║
║ Written in Nano ║
╚═══════════════════════════════════════════════════════════════════════════╝
USAGE: nano mandelbrot.nano [OPTIONS]
OPTIONS:
-h, --help Show this help message
-w, --width <N> Set output width in characters (default: 80)
-H, --height <N> Set output height in lines (default: 40)
-i, --iterations <N> Maximum iterations per point (default: 100)
-p, --preset <NAME> Use a preset view (see -l for list)
-l, --list List available presets
-g, --gradient <TYPE> Gradient type: 'ascii', 'block', or custom string
-x, --x-min <N> Minimum real value (default: -2.5)
-X, --x-max <N> Maximum real value (default: 1.0)
-y, --y-min <N> Minimum imaginary value (default: -1.0)
-Y, --y-max <N> Maximum imaginary value (default: 1.0)
-a, --animate Show zoom animation
-z, --zoom <PRESET> Zoom into a preset location
EXAMPLES:
nano mandelbrot.nano
nano mandelbrot.nano -w 120 -H 60 -i 200
nano mandelbrot.nano -p seahorse
nano mandelbrot.nano -g block -i 50
nano mandelbrot.nano -x -0.5 -X 0.5 -y -0.5 -Y 0.5
")
}
fn show_presets() {
print("\nAvailable Presets:\n")
print("─────────────────────────────────────────────────────")
for name, preset in PRESETS {
print(" " ++ name ++ ":")
print(" " ++ preset.name)
print(" Real: [" ++ str(preset.x_min) ++ ", " ++ str(preset.x_max) ++ "]")
print(" Imag: [" ++ str(preset.y_min) ++ ", " ++ str(preset.y_max) ++ "]")
print("")
}
}
# ============================================================================
# Animation Functions
# ============================================================================
# Linear interpolation
fn lerp(a, b, t) {
a + (b - a) * t
}
# Animate zoom into a location
fn animate_zoom(target_preset, config) {
let target = PRESETS[target_preset]
if target == nil {
print("Unknown preset for zoom: " ++ target_preset)
return nil
}
let frames = 20
let start = {
x_min: DEFAULT_X_MIN,
x_max: DEFAULT_X_MAX,
y_min: DEFAULT_Y_MIN,
y_max: DEFAULT_Y_MAX
}
print("\nZooming into: " ++ target.name ++ "\n")
var frame = 0
while frame <= frames {
let t = frame / frames
# Use smooth easing
let eased = t * t * (3.0 - 2.0 * t)
let current_config = {
width: config.width,
height: config.height,
max_iter: config.max_iter,
gradient: config.gradient,
x_min: lerp(start.x_min, target.x_min, eased),
x_max: lerp(start.x_max, target.x_max, eased),
y_min: lerp(start.y_min, target.y_min, eased),
y_max: lerp(start.y_max, target.y_max, eased)
}
# Clear screen (ANSI escape)
print("\x1b[2J\x1b[H")
print("Frame " ++ str(frame) ++ "/" ++ str(frames) ++ " - " ++ target.name)
print(render_mandelbrot(current_config))
# Delay
sleep(100)
frame = frame + 1
}
}
# ============================================================================
# Statistics and Info
# ============================================================================
fn show_render_info(config) {
print("┌─────────────────────────────────────────┐")
print("│ Mandelbrot Fractal │")
print("├─────────────────────────────────────────┤")
print("│ Size: " ++ str(config.width) ++ " x " ++ str(config.height))
print("│ Iterations: " ++ str(config.max_iter))
print("│ Real: [" ++ str(config.x_min) ++ ", " ++ str(config.x_max) ++ "]")
print("│ Imag: [" ++ str(config.y_min) ++ ", " ++ str(config.y_max) ++ "]")
print("└─────────────────────────────────────────┘")
print("")
}
# ============================================================================
# Interactive Mode
# ============================================================================
fn interactive_mode(initial_config) {
var config = initial_config
var running = true
print("\nInteractive Mode - Commands:")
print(" w/a/s/d - Pan up/left/down/right")
print(" +/- - Zoom in/out")
print(" r - Reset to default view")
print(" p - Print current coordinates")
print(" q - Quit")
print("")
while running {
# Render current view
print("\x1b[2J\x1b[H") # Clear screen
show_render_info(config)
print(render_mandelbrot(config))
# Get input
let cmd = input("Command: ")
let x_range = config.x_max - config.x_min
let y_range = config.y_max - config.y_min
let pan_amount = 0.1
let zoom_factor = 0.5
if cmd == "q" {
running = false
} else if cmd == "w" {
# Pan up
config.y_min = config.y_min - y_range * pan_amount
config.y_max = config.y_max - y_range * pan_amount
} else if cmd == "s" {
# Pan down
config.y_min = config.y_min + y_range * pan_amount
config.y_max = config.y_max + y_range * pan_amount
} else if cmd == "a" {
# Pan left
config.x_min = config.x_min - x_range * pan_amount
config.x_max = config.x_max - x_range * pan_amount
} else if cmd == "d" {
# Pan right
config.x_min = config.x_min + x_range * pan_amount
config.x_max = config.x_max + x_range * pan_amount
} else if cmd == "+" or cmd == "=" {
# Zoom in
let x_center = (config.x_min + config.x_max) / 2.0
let y_center = (config.y_min + config.y_max) / 2.0
let new_x_range = x_range * zoom_factor
let new_y_range = y_range * zoom_factor
config.x_min = x_center - new_x_range / 2.0
config.x_max = x_center + new_x_range / 2.0
config.y_min = y_center - new_y_range / 2.0
config.y_max = y_center + new_y_range / 2.0
# Increase iterations when zooming
config.max_iter = config.max_iter + 50
} else if cmd == "-" {
# Zoom out
let x_center = (config.x_min + config.x_max) / 2.0
let y_center = (config.y_min + config.y_max) / 2.0
let new_x_range = x_range / zoom_factor
let new_y_range = y_range / zoom_factor
config.x_min = x_center - new_x_range / 2.0
config.x_max = x_center + new_x_range / 2.0
config.y_min = y_center - new_y_range / 2.0
config.y_max = y_center + new_y_range / 2.0
# Decrease iterations when zooming out
config.max_iter = max(50, config.max_iter - 50)
} else if cmd == "r" {
# Reset
config.x_min = DEFAULT_X_MIN
config.x_max = DEFAULT_X_MAX
config.y_min = DEFAULT_Y_MIN
config.y_max = DEFAULT_Y_MAX
config.max_iter = DEFAULT_MAX_ITER
} else if cmd == "p" {
print("\nCurrent view:")
print(" x_min: " ++ str(config.x_min))
print(" x_max: " ++ str(config.x_max))
print(" y_min: " ++ str(config.y_min))
print(" y_max: " ++ str(config.y_max))
print(" iterations: " ++ str(config.max_iter))
input("Press Enter to continue...")
}
}
}
# ============================================================================
# Color Output (ANSI 256 colors)
# ============================================================================
fn iter_to_ansi_color(iter, max_iter) {
if iter >= max_iter {
"\x1b[48;5;0m " # Black background
} else {
# Map iteration to color (using 256-color palette)
let color_index = floor((iter / max_iter) * 230) + 16
"\x1b[48;5;" ++ str(color_index) ++ "m "
}
}
fn render_mandelbrot_color(config) {
let width = config.width or DEFAULT_WIDTH
let height = config.height or DEFAULT_HEIGHT
let max_iter = config.max_iter or DEFAULT_MAX_ITER
let bounds = {
x_min: config.x_min or DEFAULT_X_MIN,
x_max: config.x_max or DEFAULT_X_MAX,
y_min: config.y_min or DEFAULT_Y_MIN,
y_max: config.y_max or DEFAULT_Y_MAX
}
var output = ""
var y = 0
while y < height {
var x = 0
var line = ""
while x < width {
let c = pixel_to_complex(x, y, width, height, bounds)
let iter = mandelbrot_iter(c, max_iter)
let colored_char = iter_to_ansi_color(iter, max_iter)
line = line ++ colored_char
x = x + 1
}
output = output ++ line ++ "\x1b[0m\n" # Reset color at end of line
y = y + 1
}
output
}
# ============================================================================
# Main Entry Point
# ============================================================================
fn main(args) {
let config = parse_args(args)
if config.help {
show_help()
return 0
}
if config.list_presets {
show_presets()
return 0
}
if config.animate and config.zoom_target != nil {
animate_zoom(config.zoom_target, config)
return 0
}
# Show info and render
show_render_info(config)
let fractal = render_mandelbrot(config)
print(fractal)
0
}
# Run main with command line arguments
main(ARGS)
This Mandelbrot fractal generator includes:
- Complex number arithmetic using Nano tables as complex number structs
- Configurable resolution (width, height)
- Adjustable iteration depth for detail control
- Multiple character gradients (ASCII and block characters)
- Full Mandelbrot view
- Seahorse Valley (famous spiral region)
- Elephant Valley
- Spiral Detail
- Lightning pattern
- Julia Island
- Mini Mandelbrot (embedded copy)
# Basic usage
nano mandelbrot.nano
# High resolution with more iterations
nano mandelbrot.nano -w 120 -H 60 -i 200
# Use a preset location
nano mandelbrot.nano -p seahorse
# Custom view coordinates
nano mandelbrot.nano -x -0.5 -X 0.5 -y -0.5 -Y 0.5
# Block characters for better terminal rendering
nano mandelbrot.nano -g block
.
.:
.:-=.
.:-=+*.
..:-=+*#%.
. ..:-==+*#%@.
.::.....:-==++*#%@@
.:-=====--=++**#%@@@
.::-=+++++++***#%%@@@
.::--=+*****####%%@@@@
.::--=+*##########%@@@@
.::--=+*###%%%%%%%@@@@@@
.::-==+*##%%@@@@@@@@@@@@@
.::-==+**#%%@@@@@@@@@@@@@@
.::-==+**##%%@@@@@@@@@@@@@@
.::-==++**##%%@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.::-==++**##%%@@@@@@@@@@@@@@@
.::-==+**##%%@@@@@@@@@@@@@@
.::-==+**#%%@@@@@@@@@@@@@@
.::-==+*##%%@@@@@@@@@@@@@
.::--=+*###%%%%%%%@@@@@@
.::--=+*##########%@@@@
.::--=+*****####%%@@@@
.::-=+++++++***#%%@@@
.:-=====--=++**#%@@@
.::.....:-==++*#%@@
. ..:-==+*#%@.
..:-=+*#%.
.:-=+*.
.:-=.
.:
.
48 input, 6,700 output
Compile errors I got: