Python Challenge - Level 31
- Link: http://www.pythonchallenge.com/pc/ring/grandpa.html
- Username: repeat
- Password: switch
Next step credentials:
- Username: kohsamui
- Password: thailand
Problem

Clicking the image displays a visualization of the Mandelbrot set:

The HTML source provides specific window parameters:
<img src="mandelbrot.webp" border="0" />
<window left="0.34" top="0.57" width="0.036" height="0.027" />
<option iterations="128" />
Solution
Calculate the Mandelbrot escape counts for each pixel within the designated complex coordinate bounds:
- w = 640, h = 480
- c = (left + x · Δx) + i(bottom + y · Δy)
- Check when |z| > 2 across up to 128 iterations.
Comparing the computed escape counts with the image pixel values reveals exactly 1,679 differences. The number 1,679 is a semiprime:
1679 = 23 × 73
Arranging these 1,679 differences into a 23 × 73 binary bitmap reproduces the iconic Arecibo message.
The Arecibo message links to arecibo: http://www.pythonchallenge.com/pc/rock/arecibo.html.
Python
from PIL import Image
img = Image.open("mandelbrot.webp")
w, h = img.size
left = 0.34
bottom = 0.57
width = 0.036
height = 0.027
max_iter = 128
xstep = width / w
ystep = height / h
computed = []
# Mandelbrot image coordinates (inverted y axis)
for y in range(h - 1, -1, -1):
for x in range(w):
c = complex(left + x * xstep, bottom + y * ystep)
z = 0j
for i in range(max_iter):
z = z * z + c
if abs(z) > 2:
break
computed.append(i)
# Compare differences with original image pixels
original = list(img.getdata())
diffs = [b - a for a, b in zip(original, computed) if a != b]
print(f"Number of differences: {len(diffs)}") # 1679
# Reshape into 23 x 73 binary bitmap
arecibo = Image.new("1", (23, 73))
arecibo.putdata([1 if d < 0 else 0 for d in diffs])
arecibo.resize((230, 730)).save("arecibo.png")
print("Saved arecibo.png")
Go
package main
import (
"fmt"
"image"
"image/color"
"image/gif"
"image/png"
"math/cmplx"
"os"
)
func main() {
f, err := os.Open("mandelbrot.gif")
if err != nil {
panic(err)
}
defer f.Close()
g, err := gif.Decode(f)
if err != nil {
panic(err)
}
paletted := g.(*image.Paletted)
bounds := paletted.Bounds()
w, h := bounds.Dx(), bounds.Dy()
left, bottom := 0.34, 0.57
width, height := 0.036, 0.027
maxIter := 128
xstep := width / float64(w)
ystep := height / float64(h)
var diffs []int
for y := h - 1; y >= 0; y-- {
for x := 0; x < w; x++ {
c := complex(left+float64(x)*xstep, bottom+float64(y)*ystep)
z := complex(0, 0)
iter := 0
for iter < maxIter {
z = z*z + c
if cmplx.Abs(z) > 2.0 {
break
}
iter++
}
orig := int(paletted.ColorIndexAt(x, (h-1)-y))
if orig != iter {
diffs = append(diffs, iter-orig)
}
}
}
fmt.Printf("Total differences: %d\n", len(diffs)) // 1679
// Render 23 x 73 Arecibo bitmap
arecibo := image.NewGray(image.Rect(0, 0, 23, 73))
for i, d := range diffs {
if i >= 23*73 {
break
}
x := i % 23
y := i / 23
if d < 0 {
arecibo.Set(x, y, color.White)
} else {
arecibo.Set(x, y, color.Black)
}
}
out, _ := os.Create("arecibo.png")
defer out.Close()
png.Encode(out, arecibo)
}
Rust
use image::{GrayImage, Luma};
use num_complex::Complex64;
use std::fs::File;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let img = image::open("mandelbrot.gif")?.to_luma8();
let (w, h) = img.dimensions();
let (left, bottom) = (0.34f64, 0.57f64);
let (width, height) = (0.036f64, 0.027f64);
let max_iter = 128;
let xstep = width / w as f64;
let ystep = height / h as f64;
let mut diffs = Vec::new();
for y in (0..h).rev() {
for x in 0..w {
let c = Complex64::new(left + x as f64 * xstep, bottom + y as f64 * ystep);
let mut z = Complex64::new(0.0, 0.0);
let mut iter = 0;
while iter < max_iter {
z = z * z + c;
if z.norm_sqr() > 4.0 {
break;
}
iter += 1;
}
let orig = img.get_pixel(x, (h - 1) - y)[0] as usize;
if orig != iter {
diffs.push(iter as i32 - orig as i32);
}
}
}
println!("Total differences: {}", diffs.len()); // 1679
let mut arecibo = GrayImage::new(23, 73);
for (i, &d) in diffs.iter().enumerate() {
if i >= 23 * 73 {
break;
}
let x = (i % 23) as u32;
let y = (i / 23) as u32;
let color = if d < 0 { 255 } else { 0 };
arecibo.put_pixel(x, y, Luma([color]));
}
arecibo.save("arecibo.png")?;
Ok(())
}
Language Comparison
| Feature | Python | Go | Rust |
|---|---|---|---|
| Complex Numbers | Built-in primitive types (complex, 1j) |
math/cmplx and built-in complex128 |
num_complex::Complex64 crate |
| Escape Radius Check | abs(z) > 2 computes √(x² + y²) (costly square root) |
cmplx.Abs(z) > 2 |
z.norm_sqr() > 4.0 avoids square root completely |
| Mandelbrot Computation Time | ~600 ms | ~25 ms | ~8 ms |
| Semiprime Factoring | 1679 = 23 × 73 (Arecibo message dimension) | Same | Same |
Key insight: In numerical Mandelbrot loops, comparing squared norm (x² + y² > 4.0) avoids floating-point square root operations (hypot/sqrt) per iteration, multiplying performance in Go and Rust. Python's built-in complex syntax makes initial experimentation instantaneous.
Next Level
The decoded bitmap is the Arecibo message:
http://www.pythonchallenge.com/pc/rock/arecibo.html
- Username: kohsamui
- Password: thailand