|
| 1 | +"""A python reading a book, drawn by Claude Fable 5.1. |
| 2 | +
|
| 3 | +Model: anthropic/claude-fable-5.1 |
| 4 | +Date: 2026-09-04 |
| 5 | +Prompt: Write a Python turtle program that draws a python reading a book. |
| 6 | +
|
| 7 | +Generated for snake-bench, Real Python's vibe check for new AI models: |
| 8 | +https://github.com/devcademy/snake-bench |
| 9 | +
|
| 10 | +The code is exactly what the model returned. Nothing was fixed up. |
| 11 | +Run it with: python claude-fable-5-1-python-reading-a-book.py |
| 12 | +""" |
| 13 | + |
| 14 | +import math |
| 15 | +import turtle |
| 16 | + |
| 17 | +# ---------------------------------------------------------------- setup |
| 18 | +screen = turtle.Screen() |
| 19 | +screen.setup(800, 600) |
| 20 | +screen.bgcolor("#f7f1e1") |
| 21 | +screen.title("A Python Reading a Book") |
| 22 | +screen.tracer(0) # draw instantly, then update once |
| 23 | + |
| 24 | +t = turtle.Turtle() |
| 25 | +t.hideturtle() |
| 26 | +t.speed(0) |
| 27 | + |
| 28 | +SNAKE_DARK = "#2e7d32" |
| 29 | +SNAKE_LIGHT = "#9ccc65" |
| 30 | +SNAKE_OUTLINE = "#1b4d1e" |
| 31 | +COIL_X = -290 # center of the coiled body |
| 32 | + |
| 33 | + |
| 34 | +# -------------------------------------------------------------- helpers |
| 35 | +def jump(x, y, heading=None): |
| 36 | + """Move without drawing, optionally set heading.""" |
| 37 | + t.penup() |
| 38 | + t.goto(x, y) |
| 39 | + if heading is not None: |
| 40 | + t.setheading(heading) |
| 41 | + t.pendown() |
| 42 | + |
| 43 | + |
| 44 | +def polygon(points, fill, outline=None): |
| 45 | + t.color(outline or fill, fill) |
| 46 | + t.pensize(2) |
| 47 | + t.penup() |
| 48 | + t.goto(points[0]) |
| 49 | + t.pendown() |
| 50 | + t.begin_fill() |
| 51 | + for p in points[1:]: |
| 52 | + t.goto(p) |
| 53 | + t.goto(points[0]) |
| 54 | + t.end_fill() |
| 55 | + |
| 56 | + |
| 57 | +def arc(radius, extent, w_start, w_end, steps=30): |
| 58 | + """Draw an arc whose pen width tapers smoothly from w_start to w_end.""" |
| 59 | + for i in range(steps): |
| 60 | + t.pensize(w_start + (w_end - w_start) * i / (steps - 1)) |
| 61 | + t.circle(radius, extent / steps) |
| 62 | + |
| 63 | + |
| 64 | +# ---------------------------------------------------------------- scene |
| 65 | +def draw_table(): |
| 66 | + polygon([(-400, -215), (400, -215), (400, -300), (-400, -300)], |
| 67 | + "#c9a46c", "#a67c48") |
| 68 | + t.color("#a67c48") |
| 69 | + t.pensize(3) |
| 70 | + jump(-400, -215) |
| 71 | + t.goto(400, -215) |
| 72 | + |
| 73 | + |
| 74 | +def draw_book(): |
| 75 | + # hard cover |
| 76 | + polygon([(-175, -215), (0, -235), (175, -215), |
| 77 | + (175, -95), (0, -115), (-175, -95)], |
| 78 | + "#7a3b1e", "#4d240f") |
| 79 | + # left and right pages (slightly tilted toward the spine) |
| 80 | + polygon([(-160, -200), (-4, -222), (-4, -118), (-160, -98)], |
| 81 | + "#fffaf0", "#d8cfbf") |
| 82 | + polygon([(4, -222), (160, -200), (160, -98), (4, -118)], |
| 83 | + "#fffaf0", "#d8cfbf") |
| 84 | + # spine |
| 85 | + t.color("#4d240f") |
| 86 | + t.pensize(3) |
| 87 | + jump(0, -235) |
| 88 | + t.goto(0, -115) |
| 89 | + |
| 90 | + # lines of "text" on each page |
| 91 | + t.color("#666666") |
| 92 | + t.pensize(2) |
| 93 | + lengths = [105, 90, 110, 70, 100, 85] |
| 94 | + for i, length in enumerate(lengths): |
| 95 | + d = 18 + 13 * i # distance below the top edge |
| 96 | + # left page: top edge runs from (-160,-98) to (-4,-118) |
| 97 | + x0 = -145 |
| 98 | + x1 = x0 + length |
| 99 | + jump(x0, -118 - d + (-4 - x0) * 20 / 156) |
| 100 | + t.goto(x1, -118 - d + (-4 - x1) * 20 / 156) |
| 101 | + # right page: top edge runs from (4,-118) to (160,-98) |
| 102 | + x0 = 20 |
| 103 | + x1 = x0 + length |
| 104 | + jump(x0, -118 - d + (x0 - 4) * 20 / 156) |
| 105 | + t.goto(x1, -118 - d + (x1 - 4) * 20 / 156) |
| 106 | + |
| 107 | + |
| 108 | +def draw_body(scale, color): |
| 109 | + """Coiled body that spirals outward, then rises into a neck over the book.""" |
| 110 | + t.color(color) |
| 111 | + jump(COIL_X, -140, 0) |
| 112 | + widths = [6, 12, 18, 24, 30, 34] |
| 113 | + radii = [20, 35, 50, 65, 80] |
| 114 | + for i, r in enumerate(radii): |
| 115 | + arc(r, 180, widths[i] * scale, widths[i + 1] * scale) |
| 116 | + # neck: up and then arching over toward the book |
| 117 | + arc(-70, 180, 34 * scale, 34 * scale) |
| 118 | + arc(-160, 75, 34 * scale, 34 * scale) |
| 119 | + |
| 120 | + |
| 121 | +def draw_head(): |
| 122 | + hx, hy = t.position() |
| 123 | + ang = math.radians(t.heading()) |
| 124 | + fx, fy = math.cos(ang), math.sin(ang) # forward direction |
| 125 | + px, py = -fy, fx # perpendicular (left) |
| 126 | + |
| 127 | + cx, cy = hx + 18 * fx, hy + 18 * fy # head center |
| 128 | + t.penup() |
| 129 | + t.goto(cx, cy) |
| 130 | + t.dot(66, SNAKE_OUTLINE) |
| 131 | + t.dot(60, SNAKE_DARK) |
| 132 | + # snout |
| 133 | + t.goto(cx + 22 * fx, cy + 22 * fy) |
| 134 | + t.dot(44, SNAKE_OUTLINE) |
| 135 | + t.dot(40, SNAKE_DARK) |
| 136 | + # lighter "chin" |
| 137 | + t.goto(cx + 26 * fx, cy + 26 * fy) |
| 138 | + t.dot(24, SNAKE_LIGHT) |
| 139 | + |
| 140 | + # eyes (looking down at the book) with reading glasses |
| 141 | + eyes = [] |
| 142 | + for side in (-1, 1): |
| 143 | + ex = cx + 10 * fx + side * 13 * px |
| 144 | + ey = cy + 10 * fy + side * 13 * py |
| 145 | + eyes.append((ex, ey)) |
| 146 | + t.goto(ex, ey) |
| 147 | + t.dot(16, "white") |
| 148 | + t.goto(ex + 3 * fx, ey + 3 * fy) |
| 149 | + t.dot(7, "black") |
| 150 | + # glasses rim |
| 151 | + t.color("black") |
| 152 | + t.pensize(2) |
| 153 | + jump(ex, ey - 11, 0) |
| 154 | + t.circle(11) |
| 155 | + (e1x, e1y), (e2x, e2y) = eyes |
| 156 | + # bridge of the glasses |
| 157 | + jump(e1x + 11 * px, e1y + 11 * py) |
| 158 | + t.goto(e2x - 11 * px, e2y - 11 * py) |
| 159 | + # temple arms going back along the head |
| 160 | + jump(e2x + 11 * px, e2y + 11 * py) |
| 161 | + t.goto(e2x + 11 * px - 26 * fx, e2y + 11 * py - 26 * fy) |
| 162 | + jump(e1x - 11 * px, e1y - 11 * py) |
| 163 | + t.goto(e1x - 11 * px - 26 * fx, e1y - 11 * py - 26 * fy) |
| 164 | + |
| 165 | + # forked tongue |
| 166 | + t.color("#e53935") |
| 167 | + t.pensize(3) |
| 168 | + tx, ty = cx + 42 * fx, cy + 42 * fy |
| 169 | + jump(tx, ty, math.degrees(ang)) |
| 170 | + t.forward(18) |
| 171 | + fork = t.position() |
| 172 | + t.left(30) |
| 173 | + t.forward(9) |
| 174 | + jump(*fork, math.degrees(ang)) |
| 175 | + t.right(30) |
| 176 | + t.forward(9) |
| 177 | + |
| 178 | + return cx, cy |
| 179 | + |
| 180 | + |
| 181 | +def draw_thought_bubble(head_x, head_y): |
| 182 | + t.penup() |
| 183 | + t.color("#555555", "white") |
| 184 | + # trail of small bubbles from the head |
| 185 | + for (bx, by, r) in [(head_x + 26, head_y + 36, 8), |
| 186 | + (head_x + 46, head_y + 66, 12)]: |
| 187 | + t.goto(bx, by) |
| 188 | + t.dot(r * 2 + 4, "#555555") |
| 189 | + t.dot(r * 2, "white") |
| 190 | + # main bubble |
| 191 | + bx, by, r = -40, 100, 68 |
| 192 | + t.goto(bx, by) |
| 193 | + t.dot(r * 2 + 4, "#555555") |
| 194 | + t.dot(r * 2, "white") |
| 195 | + t.color("#1a237e") |
| 196 | + t.goto(bx, by + 2) |
| 197 | + t.write("import", align="center", font=("Courier", 13, "bold")) |
| 198 | + t.goto(bx, by - 20) |
| 199 | + t.write("antigravity", align="center", font=("Courier", 13, "bold")) |
| 200 | + |
| 201 | + |
| 202 | +def draw_caption(): |
| 203 | + t.penup() |
| 204 | + t.goto(0, 235) |
| 205 | + t.color("#4d240f") |
| 206 | + t.write("A Python reading a book", align="center", |
| 207 | + font=("Georgia", 20, "italic")) |
| 208 | + |
| 209 | + |
| 210 | +# ----------------------------------------------------------------- draw |
| 211 | +draw_table() |
| 212 | +draw_book() |
| 213 | +draw_body(1.0, SNAKE_OUTLINE) # dark outline pass (slightly wider) |
| 214 | +draw_body(0.85, SNAKE_DARK) # main body color |
| 215 | +draw_body(0.35, SNAKE_LIGHT) # dorsal stripe |
| 216 | +cx, cy = draw_head() |
| 217 | +draw_thought_bubble(cx, cy) |
| 218 | +draw_caption() |
| 219 | + |
| 220 | +screen.update() |
| 221 | +turtle.done() |
0 commit comments