Python Challenge - Level 22
- Link: http://www.pythonchallenge.com/pc/hex/copper.html
- Username: butter
- Password: fly
Problem

The image white.gif contains multiple frames. Inspecting the frames reveals that each frame is an empty black background with a single bright pixel positioned near (100, 100).
The coordinate displacement (Δx, Δy) = (x - 100, y - 100) mimics a joystick movement. When the displacement is (0, 0), it indicates a pen-lift / move to the next letter position.
Solution
Track the cumulative path traced by the bright pixel:
- Compute (Δx, Δy) = (x - 100, y - 100).
- When Δx = 0 and Δy = 0, move the drawing cursor forward (e.g. +50 px to the right) to begin a new character.
- Draw lines or points along the cursor positions.
The reconstructed strokes spell out the word bonus.
Python
from PIL import Image, ImageDraw
img = Image.open("white.gif")
canvas = Image.new("RGB", (500, 200), (0, 0, 0))
draw = ImageDraw.Draw(canvas)
cx, cy = 0, 100
for frame in range(img.n_frames):
img.seek(frame)
# Get bounding box of the non-zero pixel
bbox = img.getbbox()
if not bbox:
continue
x, y = bbox[0], bbox[1]
dx = x - 100
dy = y - 100
if dx == 0 and dy == 0:
cx += 45
cy = 100
cx += dx
cy += dy
draw.point((cx, cy), fill=(255, 255, 255))
canvas.save("bonus.png")
Go
package main
import (
"image"
"image/color"
"image/gif"
"image/png"
"os"
)
func main() {
f, err := os.Open("white.gif")
if err != nil {
panic(err)
}
defer f.Close()
g, err := gif.DecodeAll(f)
if err != nil {
panic(err)
}
canvas := image.NewRGBA(image.Rect(0, 0, 500, 200))
cx, cy := 0, 100
for _, frame := range g.Image {
// Find non-zero pixel in frame
var px, py int
found := false
bounds := frame.Bounds()
for y := bounds.Min.Y; y < bounds.Max.Y && !found; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
if r, g, b, _ := frame.At(x, y).RGBA(); r > 0 || g > 0 || b > 0 {
px, py = x, y
found = true
break
}
}
}
if !found {
continue
}
dx := px - 100
dy := py - 100
if dx == 0 && dy == 0 {
cx += 45
cy = 100
}
cx += dx
cy += dy
if cx >= 0 && cx < 500 && cy >= 0 && cy < 200 {
canvas.Set(cx, cy, color.White)
}
}
out, _ := os.Create("bonus.png")
defer out.Close()
png.Encode(out, canvas)
}
Rust
use image::codecs::gif::GifDecoder;
use image::{AnimationDecoder, ImageBuffer, Rgb, RgbImage};
use std::fs::File;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = File::open("white.gif")?;
let decoder = GifDecoder::new(file)?;
let frames = decoder.into_frames().collect_frames()?;
let mut canvas: RgbImage = ImageBuffer::new(500, 200);
let (mut cx, mut cy) = (0i32, 100i32);
for frame in frames {
let buffer = frame.buffer();
let mut pt = None;
for (x, y, pixel) in buffer.enumerate_pixels() {
if pixel[0] > 0 || pixel[1] > 0 || pixel[2] > 0 {
pt = Some((x as i32, y as i32));
break;
}
}
if let Some((px, py)) = pt {
let dx = px - 100;
let dy = py - 100;
if dx == 0 && dy == 0 {
cx += 45;
cy = 100;
}
cx += dx;
cy += dy;
if cx >= 0 && cx < 500 && cy >= 0 && cy < 200 {
canvas.put_pixel(cx as u32, cy as u32, Rgb([255, 255, 255]));
}
}
}
canvas.save("bonus.png")?;
Ok(())
}
Language Comparison
| Feature | Python | Go | Rust |
|---|---|---|---|
| Multi-frame GIF Decoding | img.seek(frame) iterator / index |
gif.DecodeAll() loads all frames into []*image.Paletted |
GifDecoder::new().into_frames() stream iterator |
| Bounding Box / Active Pixel | img.getbbox() instantly finds non-zero bounding box |
Manual loop scanning pixels or custom bounding box function | Iteration over enumerate_pixels() |
| Drawing Canvas | ImageDraw.Draw(canvas).point() |
Direct canvas.Set(x, y, color.White) |
Direct canvas.put_pixel(x, y, pixel) |
Key insight: Go's standard library gif.DecodeAll parses every frame of animated GIFs out of the box with zero external libraries. Rust's image crate provides a streaming animation decoder that yields framed buffers with accurate disposal and delay metadata.
Next Level
The letters spell bonus:
Recommended ReadingSponsored
Python Challenge Solutions
Python Challenge Solutions

