Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Created March 7, 2020 11:45
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save peterhellberg/3c5b30465a258f6b688a8f11955b12ba to your computer and use it in GitHub Desktop.
Save peterhellberg/3c5b30465a258f6b688a8f11955b12ba to your computer and use it in GitHub Desktop.
Sprator using gfx inspired by https://github.com/yurkth/sprator
package main
import (
"flag"
"image"
"image/color"
"time"
"github.com/peterhellberg/gfx"
)
// Sprator algorithm as described at
// https://github.com/yurkth/sprator
//
// 1. Generate 4x8 white noise.
// 2. Change the state according to the following rules.
// - Any live cell with two or three neighbors survives.
// - Any dead cell with one or less live neighbors becomes a live cell.
// - All other live cells die in the next generation. Similarly, all other dead cells stay dead.
// 3. Repeat steps 2 several times.
// 4. Flip and add a outline, complete!
func main() {
var seed int64
var variant string
flag.Int64Var(&seed, "seed", time.Now().Unix(), "Seed value to use for the state")
flag.StringVar(&variant, "variant", "random", "Variant of the initial state [random, simplex]")
flag.Parse()
var state State
switch variant {
case "simplex":
state = SimplexState(seed)
default:
variant = "random"
state = RandomState(seed)
}
for i := 0; i < 2; i++ {
state = state.Next()
}
dst := gfx.NewImage(10, 10)
bc := gfx.BlockColors[int(seed)%len(gfx.BlockColors)]
t := gfx.ColorTransparent
for x := 1; x < 5; x++ {
for y := 1; y < 9; y++ {
if state.At(x-1, y-1) {
dst.Set(x, y, bc.Light)
dst.Set(5+5-x-1, y, bc.Light)
} else {
dst.Set(x, y, t)
dst.Set(5+5-x-1, y, t)
}
}
}
out := gfx.NewImage(10, 10)
for x := 0; x < 10; x++ {
for y := 0; y < 10; y++ {
if c := countNeighborhood(dst, x, y, bc.Light); c > 0 {
out.Set(x, y, bc.Dark)
}
}
}
gfx.DrawOver(out, out.Bounds(), dst, gfx.ZP)
src := gfx.NewScaledImage(out, 10)
fn := gfx.Sprintf("/tmp/gfx-sprator-%06d-%s.png", seed, variant)
gfx.Log("Saving generated sprator to %s", fn)
gfx.SavePNG(fn, src)
}
// ExampleState returns the starting state in the
// original README on https://github.com/yurkth/sprator
//
// ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░
// ░░▒▒▒▒▓▓▒▒ ░░▓▓▓▓▒▒▓▓ ░░▒▒▒▒▒▒▒▒
// ░░▓▓▒▒▒▒▒▒ ░░▒▒▒▒▓▓▓▓ ░░▓▓▒▒▒▒▓▓
// ░░▒▒▓▓▒▒▓▓ ░░▒▒▒▒▒▒▒▒ ░░▓▓▓▓▓▓▓▓
// ░░▒▒▓▓▓▓▒▒ ➤ ░░▒▒▓▓▒▒▒▒ ➤ ░░▓▓▒▒▓▓▓▓
// ░░▓▓▒▒▒▒▒▒ ➤ ░░▒▒▒▒▒▒▓▓ ➤ ░░▓▓▓▓▓▓▒▒
// ░░▓▓▒▒▓▓▒▒ ░░▒▒▒▒▒▒▒▒ ░░▓▓▓▓▓▓▓▓
// ░░▒▒▓▓▒▒▓▓ ░░▒▒▒▒▒▒▒▒ ░░▓▓▓▓▓▓▓▓
// ░░▒▒▒▒▒▒▓▓ ░░▓▓▓▓▓▓▒▒ ░░▒▒▓▓▒▒▓▓
// ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░
//
func ExampleState() State {
return State(0x8a516a14)
}
func RandomState(seed int64) (state State) {
gfx.RandSeed(seed)
for x := 0; x < 4; x++ {
for y := 0; y < 8; y++ {
state.Set(x, y, gfx.RandIntn(2) == 0)
}
}
return
}
func SimplexState(seed int64) (state State) {
simplex := gfx.NewSimplexNoise(seed)
for x := 0; x < 4; x++ {
for y := 0; y < 8; y++ {
state.Set(x, y, simplex.Noise2D(float64(x), float64(y)) > 0)
}
}
return
}
type State uint32
func (s State) At(x, y int) bool {
return hasBit(s, (y*4)+x)
}
func (s *State) Set(x, y int, b bool) {
if b {
*s = setBit(*s, (y*4)+x)
} else {
*s = clearBit(*s, (y*4)+x)
}
}
func (s State) Next() State {
var next State
for x := 0; x < 4; x++ {
for y := 0; y < 8; y++ {
n := s.Neighborhood(x, y)
if s.At(x, y) {
next.Set(x, y, n == 2 || n == 3)
} else {
next.Set(x, y, n <= 1)
}
}
}
return next
}
func (s State) Neighborhood(x, y int) int {
var n int
if y >= 1 && s.At(x, y-1) {
n++
}
if y <= 6 && s.At(x, y+1) {
n++
}
if x >= 1 && s.At(x-1, y) {
n++
}
if x <= 2 && s.At(x+1, y) {
n++
}
return n
}
func setBit(s State, pos int) State {
s |= (1 << pos)
return s
}
func clearBit(s State, pos int) State {
return s &^ (1 << pos)
}
func hasBit(s State, pos int) bool {
return (s & (1 << pos)) > 0
}
func countNeighborhood(src image.Image, x, y int, c color.Color) int {
var n int
e := func(c1, c2 color.Color) bool {
c1r, c1g, c1b, c1a := c1.RGBA()
c2r, c2g, c2b, c2a := c2.RGBA()
return c1r == c2r && c1g == c2g && c1b == c2b && c1a == c2a
}
if e(src.At(x, y-1), c) {
n++
}
if e(src.At(x, y+1), c) {
n++
}
if e(src.At(x-1, y), c) {
n++
}
if e(src.At(x+1, y), c) {
n++
}
return n
}
@peterhellberg
Copy link
Author

generate-sprators.sh

#!/usr/bin/env bash

go build -o gfx-sprator gfx-sprator.go

for i in {0..255}
do
  ./gfx-sprator -variant random -seed $i
  ./gfx-sprator -variant simplex -seed $i
done

Spritesheet generated using spriter.sh

tiling-0/sprite-complete
tiling-256/sprite-complete

@peterhellberg
Copy link
Author

With some more padding

sprite-complete with padding

@peterhellberg
Copy link
Author

peterhellberg commented Mar 7, 2020

Using the seed as the state (first variant of each group of three same colored sprators)

sprite-complete

package main

import (
	"flag"
	"image"
	"image/color"
	"time"

	"github.com/peterhellberg/gfx"
)

// Sprator algorithm as described at
// https://github.com/yurkth/sprator
//
// 1. Generate 4x8 white noise.
// 2. Change the state according to the following rules.
// 	- Any live cell with two or three neighbors survives.
//  - Any dead cell with one or less live neighbors becomes a live cell.
//  - All other live cells die in the next generation. Similarly, all other dead cells stay dead.
// 3. Repeat steps 2 several times.
// 4. Flip and add a outline, complete!

func main() {
	var seed int64

	var variant string

	flag.Int64Var(&seed, "seed", time.Now().Unix(), "Seed value to use for the state")
	flag.StringVar(&variant, "variant", "random", "Variant of the initial state [random, simplex]")

	flag.Parse()

	var state State

	switch variant {
	case "binary":
		state = State(uint32(seed))
	case "simplex":
		state = SimplexState(seed)
	default:
		variant = "random"
		state = RandomState(seed)
	}

	for i := 0; i < 2; i++ {
		state = state.Next()
	}

	dst := gfx.NewImage(10, 10)

	bc := gfx.BlockColors[int(seed)%len(gfx.BlockColors)]

	t := gfx.ColorTransparent

	for x := 1; x < 5; x++ {
		for y := 1; y < 9; y++ {
			if state.At(x-1, y-1) {
				dst.Set(x, y, bc.Light)
				dst.Set(5+5-x-1, y, bc.Light)
			} else {
				dst.Set(x, y, t)
				dst.Set(5+5-x-1, y, t)
			}
		}
	}

	out := gfx.NewImage(10, 10)

	for x := 0; x < 10; x++ {
		for y := 0; y < 10; y++ {
			if c := countNeighborhood(dst, x, y, bc.Light); c > 0 {
				out.Set(x, y, bc.Dark)
			}
		}
	}

	gfx.DrawOver(out, out.Bounds(), dst, gfx.ZP)

	src := gfx.NewScaledImage(out, 10)

	pad := gfx.NewImage(160, 160)

	gfx.DrawOver(pad, pad.Bounds(), src, gfx.Pt(-30, -30))

	fn := gfx.Sprintf("/tmp/gfx-sprator-%06d-%s.png", seed, variant)

	gfx.Log("Saving generated sprator to %s", fn)

	gfx.SavePNG(fn, pad)
}

// ExampleState returns the starting state in the
// original README on https://github.com/yurkth/sprator
//
// 		░░░░░░░░░░     ░░░░░░░░░░     ░░░░░░░░░░
// 		░░▒▒▒▒▓▓▒▒     ░░▓▓▓▓▒▒▓▓     ░░▒▒▒▒▒▒▒▒
// 		░░▓▓▒▒▒▒▒▒     ░░▒▒▒▒▓▓▓▓     ░░▓▓▒▒▒▒▓▓
// 		░░▒▒▓▓▒▒▓▓     ░░▒▒▒▒▒▒▒▒     ░░▓▓▓▓▓▓▓▓
// 		░░▒▒▓▓▓▓▒▒  ➤  ░░▒▒▓▓▒▒▒▒  ➤  ░░▓▓▒▒▓▓▓▓
// 		░░▓▓▒▒▒▒▒▒  ➤  ░░▒▒▒▒▒▒▓▓  ➤  ░░▓▓▓▓▓▓▒▒
// 		░░▓▓▒▒▓▓▒▒     ░░▒▒▒▒▒▒▒▒     ░░▓▓▓▓▓▓▓▓
// 		░░▒▒▓▓▒▒▓▓     ░░▒▒▒▒▒▒▒▒     ░░▓▓▓▓▓▓▓▓
// 		░░▒▒▒▒▒▒▓▓     ░░▓▓▓▓▓▓▒▒     ░░▒▒▓▓▒▒▓▓
// 		░░░░░░░░░░     ░░░░░░░░░░     ░░░░░░░░░░
//
func ExampleState() State {
	return State(0x8a516a14)
}

func RandomState(seed int64) (state State) {
	gfx.RandSeed(seed)

	for x := 0; x < 4; x++ {
		for y := 0; y < 8; y++ {
			state.Set(x, y, gfx.RandIntn(2) == 0)
		}
	}

	return
}

func SimplexState(seed int64) (state State) {
	simplex := gfx.NewSimplexNoise(seed)

	for x := 0; x < 4; x++ {
		for y := 0; y < 8; y++ {
			state.Set(x, y, simplex.Noise2D(float64(x), float64(y)) > 0)
		}
	}

	return
}

type State uint32

func (s State) At(x, y int) bool {
	return hasBit(s, (y*4)+x)
}

func (s *State) Set(x, y int, b bool) {
	if b {
		*s = setBit(*s, (y*4)+x)
	} else {
		*s = clearBit(*s, (y*4)+x)
	}
}

func (s State) Next() State {
	var next State

	for x := 0; x < 4; x++ {
		for y := 0; y < 8; y++ {
			n := s.Neighborhood(x, y)

			if s.At(x, y) {
				next.Set(x, y, n == 2 || n == 3)
			} else {
				next.Set(x, y, n <= 1)
			}
		}
	}

	return next
}

func (s State) Neighborhood(x, y int) int {
	var n int

	if y >= 1 && s.At(x, y-1) {
		n++
	}

	if y <= 6 && s.At(x, y+1) {
		n++
	}

	if x >= 1 && s.At(x-1, y) {
		n++
	}

	if x <= 2 && s.At(x+1, y) {
		n++
	}

	return n
}

func setBit(s State, pos int) State {
	s |= (1 << pos)
	return s
}

func clearBit(s State, pos int) State {
	return s &^ (1 << pos)
}

func hasBit(s State, pos int) bool {
	return (s & (1 << pos)) > 0
}

func countNeighborhood(src image.Image, x, y int, c color.Color) int {
	var n int

	e := func(c1, c2 color.Color) bool {
		c1r, c1g, c1b, c1a := c1.RGBA()
		c2r, c2g, c2b, c2a := c2.RGBA()

		return c1r == c2r && c1g == c2g && c1b == c2b && c1a == c2a
	}

	if e(src.At(x, y-1), c) {
		n++
	}

	if e(src.At(x, y+1), c) {
		n++
	}

	if e(src.At(x-1, y), c) {
		n++
	}

	if e(src.At(x+1, y), c) {
		n++
	}

	return n
}

@peterhellberg
Copy link
Author

go run gfx-sprator.go -seed 0x8a516a14 -variant binary

gfx-sprator-2320591380-binary

@peterhellberg
Copy link
Author

@peterhellberg
Copy link
Author

Seed 0 to 2047:

0-2047

@peterhellberg
Copy link
Author

Seed 2320591380 to 2320591892:

sprite-complete

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment