Python Challenge - Level 9

Problem

The page source contains two large lists of numbers in an HTML comment:

<!--
first+second=?

first:
146,399,163,403,170,393,...

second:
156,141,165,135,...
-->

Each list is a flat array of (x, y) coordinate pairs. Connect them as polygons to reveal the hidden image.

Solution

Python

Pillow's ImageDraw.polygon() accepts a flat list of (x, y) pairs:

import urllib.request, re
from PIL import Image, ImageDraw

html = urllib.request.urlopen(
    "http://www.pythonchallenge.com/pc/return/good.html"
).read().decode()

# Extract both coordinate lists from the HTML comment
blocks = re.findall(r"(first|second):\n([\d,\n]+)", html)
coords = {}
for name, data in blocks:
    nums = list(map(int, re.findall(r"\d+", data)))
    coords[name] = list(zip(nums[::2], nums[1::2]))  # pair up as (x,y) tuples

img = Image.new("RGB", (500, 500), "black")
draw = ImageDraw.Draw(img)
draw.polygon(coords["first"],  fill="white")
draw.polygon(coords["second"], fill="white")
img.show()
# Reveals a bull

Answer: bull

Go

Go's image/draw doesn't have a polygon primitive — use the golang.org/x/image/draw package or implement Bresenham polygon fill. The cleanest approach uses the freetype or gg graphics library:

package main

import (
    "fmt"
    "image"
    "image/color"
    "image/png"
    "io"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "strings"

    "github.com/fogleman/gg" // go get github.com/fogleman/gg
)

func extractCoords(data string) []float64 {
    re := regexp.MustCompile(`\d+`)
    var nums []float64
    for _, s := range re.FindAllString(data, -1) {
        n, _ := strconv.ParseFloat(s, 64)
        nums = append(nums, n)
    }
    return nums
}

func main() {
    resp, _ := http.Get("http://www.pythonchallenge.com/pc/return/good.html")
    body, _ := io.ReadAll(resp.Body)
    resp.Body.Close()

    re := regexp.MustCompile(`(?s)(first|second):\n([\d,\n]+)`)
    matches := re.FindAllStringSubmatch(string(body), -1)

    dc := gg.NewContext(500, 500)
    dc.SetRGB(0, 0, 0)
    dc.Clear()
    dc.SetRGB(1, 1, 1)

    for _, m := range matches {
        nums := extractCoords(m[2])
        dc.NewSubPath()
        for i := 0; i+1 < len(nums); i += 2 {
            dc.LineTo(nums[i], nums[i+1])
        }
        dc.ClosePath()
    }
    dc.Fill()
    dc.SavePNG("output.png")
    fmt.Println("Saved output.png") // shows a bull
}

Rust

The imageproc crate provides polygon drawing:

use image::{Rgb, RgbImage};
use imageproc::drawing::draw_polygon_mut;
use imageproc::point::Point;
use regex::Regex;

fn parse_coords(data: &str) -> Vec<Point<i32>> {
    let re = Regex::new(r"\d+").unwrap();
    let nums: Vec<i32> = re.find_iter(data)
        .map(|m| m.as_str().parse().unwrap())
        .collect();
    nums.chunks(2)
        .map(|c| Point::new(c[0], c[1]))
        .collect()
}

fn main() {
    let html = reqwest::blocking::get(
        "http://www.pythonchallenge.com/pc/return/good.html"
    ).unwrap().text().unwrap();

    let re = Regex::new(r"(?s)(first|second):\n([\d,\n]+)").unwrap();
    let mut img = RgbImage::new(500, 500);

    for cap in re.captures_iter(&html) {
        let pts = parse_coords(&cap[2]);
        draw_polygon_mut(&mut img, &pts, Rgb([255u8, 255, 255]));
    }

    img.save("output.png").unwrap();
    println!("Saved output.png"); // shows a bull
}

Language Comparison

Feature Python Go Rust
Polygon fill ImageDraw.polygon() — stdlib via Pillow No stdlib polygon — use gg or freetype imageproc::drawing::draw_polygon_mut
Coordinate pairs from flat list zip(nums[::2], nums[1::2]) — slice stepping Manual i += 2 loop .chunks(2).map(...)
Image output .show() opens viewer; .save("out.png") saves dc.SavePNG() img.save()
2D graphics stdlib depth Deep — Pillow covers most 2D needs Shallow — basic raster, no vector/polygon Shallow — image crate is basic; use imageproc for drawing

Key insight: Python's Pillow is an exceptionally rich 2D graphics library compared to what Go and Rust offer in their stdlib or even popular crates. ImageDraw.polygon() with scanline fill is a single call in Python; Go and Rust require external crates and more boilerplate. For image-manipulation puzzles and scripts, Python remains the most ergonomic choice.

Next Level

http://www.pythonchallenge.com/pc/return/bull.html
Username: huge | Password: file

Loading...