// A night scene from one number, then eight frames of it: the stars // twinkle and the lake shimmers. Change the seed and run it again. let seed = 7; let w = ps.doc_width(); let h = ps.doc_height(); let horizon = 66; let glow = 50; let frames = 8; // Integer hash in 0..999. Script functions only see their arguments. fn hash(n, seed) { let a = (n * 7919 + seed * 104729) % 1000003; (a * a + n) % 1000003 % 1000 } // Smooth value noise: rolling hills with a ridge every `period` px. fn hills(x, period, amp, seed) { let i = x / period; let t = (x % period).to_float() / period.to_float(); t = t * t * (3.0 - 2.0 * t); let a = hash(i, seed).to_float(); let b = hash(i + 1, seed).to_float(); ((a + (b - a) * t) * amp.to_float() / 1000.0).to_int() } // The sky at one pixel: six bands down to a crimson glow, blended // with a 4x4 ordered dither. fn sky_at(x, y, glow) { let sky = ["#0A0A1E", "#14193F", "#1B2A6E", "#2E2A57", "#5A1236", "#A51D45"]; let bayer = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5]; let f = y.min(glow) * 5 * 16 / glow; let band = f / 16; if f % 16 > bayer[(y % 4) * 4 + x % 4] { band += 1; } sky[band.min(5)] } // One frame of stars. Each runs its own 8-step cycle from dark to // bright, and the big ones flash a four-point glint at the top. fn draw_stars(ps, stars, f, glow, w) { for s in stars { let x = s[0]; let y = s[1]; let level = (f + s[3]) % 8; let core = if level == 0 { sky_at(x, y, glow) } else if level < 3 { "#5D5590" } else if level < 6 { if y < 20 { "#B9B4D8" } else { "#8A82B8" } } else { "#FFF3B0" }; ps.pixel_set(x, y, core); if s[2] { for d in [[1, 0], [-1, 0], [0, 1], [0, -1]] { let gx = x + d[0]; let gy = y + d[1]; if gx >= 0 && gx < w && gy >= 0 { ps.pixel_set(gx, gy, if level >= 6 { "#5D5590" } else { sky_at(gx, gy, glow) }); } } } } } // One frame of the lake: it mirrors everything above the shore, // darker, and each frame moves the ripples and the moonlight. fn draw_lake(ps, f, horizon, mx, seed, w, h) { for y in horizon..h { let depth = y - horizon; let shift = hills(depth * 7 + f * 4, 3, 3, seed) - 1; let src = (horizon - 1 - depth).max(0); for x in 0..w { let sx = (x + shift).max(0).min(w - 1); let hsl = color_to_hsl(ps.pixel_get(sx, src)); let c = color_from_hsl(hsl[0], hsl[1] * 0.9, hsl[2] * 0.7); // Moonlight on the water: a broken column under the moon. if (x - mx).abs() < 6 - depth / 5 && hash(x + y * w + f * 997, seed) > 550 { c = "#B9B4D8"; } ps.pixel_set(x, y, c); } } } for y in 0..horizon { for x in 0..w { ps.pixel_set(x, y, sky_at(x, y, glow)); } } print("sky: 6 bands, dithered"); // The moon: a lit disc, a shaded crescent, two craters. let mx = w * 3 / 4; let my = 18; for y in my - 10..=my + 10 { for x in mx - 10..=mx + 10 { let dx = x - mx; let dy = y - my; if dx * dx + dy * dy <= 90 { let sx = dx + 4; let sy = dy - 3; let lit = sx * sx + sy * sy > 80; ps.pixel_set(x, y, if lit { "#E8E9F2" } else { "#5D5590" }); } } } ps.pixel_fill(mx - 6, my + 1, mx - 5, my + 2, "#B9B4D8"); ps.pixel_set(mx - 3, my + 5, "#B9B4D8"); print("moon"); // Three ridges, far to near: each lower and darker, its crest // catching a little of the glow. let ridges = [[horizon - 30, 34, 22, "#3B2A5E", "#8A3A5E"], [horizon - 19, 22, 14, "#241D45", "#3B2A5E"], [horizon - 10, 13, 8, "#16142F", "#241D45"]]; for r in 0..3 { let top = ridges[r][0]; for x in 0..w { let y0 = top + hills(x, ridges[r][1], ridges[r][2], seed + r * 31) + hills(x, 5, 3, seed + r * 7); ps.pixel_fill(x, y0, x, horizon - 1, ridges[r][3]); ps.pixel_set(x, y0, ridges[r][4]); } } print("ridges: 3 layers"); // A pine line along the shore: stacked triangles, random heights. let trees = 0; let x = 2; while x < w - 2 { let th = 6 + hash(x, seed + 99) % 9; let base = horizon - 1; for k in 0..th { let half = (th - k) / 3; ps.pixel_fill(x - half, base - k, x + half, base - k, "#0A0A1E"); } trees += 1; x += 3 + hash(x * 5, seed) % 5; } print(`trees: ${trees}`); // Stars above the ridges and clear of the moon, each with its own // place in the twinkle cycle. let stars = []; for i in 0..70 { let sx = hash(i * 3, seed) * w / 1000; let sy = hash(i * 3 + 1, seed) * (horizon - 34) / 1000; let dx = sx - mx; let dy = sy - my; if dx * dx + dy * dy < 160 { continue; } stars.push([sx, sy, hash(i * 3 + 2, seed) > 880, hash(i * 7 + 5, seed) % 8]); } // Each frame starts as a copy of the one before, so only the stars // and the water need drawing again. for f in 0..frames { if f > 0 { ps_timeline_duplicate_frame(f - 1); } draw_stars(ps, stars, f, glow, w); draw_lake(ps, f, horizon, mx, seed, w, h); } ps_timeline_set_fps(6); print(`stars: ${stars.len()}, twinkling`); print(`lake: ${frames} frames of shimmer`);