Repository navigation
Expand file tree
/
Copy pathinvaders_print.py
More file actions
83 lines (69 loc) · 3.31 KB
/
Copy pathinvaders_print.py
File metadata and controls
83 lines (69 loc) · 3.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env python3
"""Print a grid of random pixel-art invaders on the S01 thermal printer.
Each invader is a small mirror-symmetric sprite generated by randomly filling
half its cells and reflecting -- the same trick behind GitHub identicons and
Space Invaders. Every seed is a fresh swarm.
Examples:
python invaders_print.py # 4x6 swarm
python invaders_print.py --cols 5 --rows 7
python invaders_print.py --size 7 # chunkier sprites
python invaders_print.py --seed 1234
python invaders_print.py --no-print
"""
from __future__ import annotations
import argparse
from pathlib import Path
import random
from PIL import Image, ImageDraw
from print_common import ROOT, Card
def draw_invader(rng: random.Random, size: int, cell: int) -> Image.Image:
half = (size + 1) // 2
grid = [[0] * size for _ in range(size)]
for r in range(size):
for c in range(half):
on = 1 if rng.random() < 0.5 else 0
grid[r][c] = on
grid[r][size - 1 - c] = on # mirror left -> right
img = Image.new("L", (size * cell, size * cell), 255)
d = ImageDraw.Draw(img)
for r in range(size):
for c in range(size):
if grid[r][c]:
d.rectangle((c * cell, r * cell, c * cell + cell - 1, r * cell + cell - 1), fill=0)
return img
def main() -> int:
parser = argparse.ArgumentParser(description="Print a grid of random pixel invaders on the S01 printer.")
parser.add_argument("--cols", type=int, default=4, help="Invaders across (default: 4).")
parser.add_argument("--rows", type=int, default=6, help="Invaders down (default: 6).")
parser.add_argument("--size", type=int, default=5, help="Cells per invader side (default: 5).")
parser.add_argument("--seed", type=int, default=None, help="Seed for the swarm.")
parser.add_argument("--out", type=Path, default=None)
parser.add_argument("--darkness", type=int, choices=range(1, 6), default=3)
parser.add_argument("--bottom-feed", type=int, default=24)
parser.add_argument("--no-print", action="store_true")
args = parser.parse_args()
if not 2 <= args.cols <= 7 or not 1 <= args.rows <= 12 or not 4 <= args.size <= 9:
parser.error("--cols 2-7, --rows 1-12, --size 4-9")
seed = args.seed if args.seed is not None else random.randrange(1, 100000)
rng = random.Random(seed)
print(f"Spawning {args.cols * args.rows} invaders (seed {seed}) ...")
card = Card()
gap = 18
iw = (card.inner_w - gap * (args.cols - 1)) // args.cols
cell = iw // args.size
sprite = cell * args.size
grid_w = sprite * args.cols + gap * (args.cols - 1)
grid_h = sprite * args.rows + gap * (args.rows - 1)
canvas = Image.new("L", (grid_w, grid_h), 255)
for r in range(args.rows):
for c in range(args.cols):
inv = draw_invader(rng, args.size, cell)
canvas.paste(inv, (c * (sprite + gap), r * (sprite + gap)))
card.title("INVADERS")
card.line(f"{args.cols * args.rows} unique sprites", size=12, bold=False, center=True)
card.gap(6).image(canvas, border=False)
card.footer("procedural swarm", f"seed {seed}")
out = args.out or ROOT / f"invaders_{seed}.png"
return card.finish(out, args.bottom_feed, args.darkness, do_print=not args.no_print)
if __name__ == "__main__":
raise SystemExit(main())