Add hand-placed 12x12 icon set

This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 16:24:50 -04:00
1 parent a92829f353
commit 7e47e3a924
7 files changed
+1641

No files matched your search

+176
View File
@@ -0,0 +1,176 @@
package icon_test
import (
"go/ast"
"go/parser"
"go/token"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
type decl struct {
file string
expr ast.Expr
}
var upper = regexp.MustCompile(`([a-z])([A-Z])`)
func kebab(s string) string { return strings.ToLower(upper.ReplaceAllString(s, "$1-$2")) }
// decls maps each icon's kebab name to its source.
func decls(t *testing.T) map[string]decl {
files, _ := filepath.Glob("*.go")
out := map[string]decl{}
for _, f := range files {
if strings.HasSuffix(f, "_test.go") || f == "icon.go" {
continue
}
file, err := parser.ParseFile(token.NewFileSet(), f, nil, 0)
if err != nil {
t.Fatal(err)
}
for _, d := range file.Decls {
if g, ok := d.(*ast.GenDecl); ok && g.Tok == token.VAR {
for _, sp := range g.Specs {
v := sp.(*ast.ValueSpec)
for i, n := range v.Names {
out[kebab(n.Name)] = decl{f, v.Values[i]}
}
}
}
}
}
return out
}
// build rebuilds an icon from its declared art.
func build(t *testing.T, ds map[string]decl, name string) *pixel.Sprite {
call, ok := ds[name].expr.(*ast.CallExpr)
if !ok {
t.Fatalf("%s is not a sprite call", name)
}
sel := call.Fun.(*ast.SelectorExpr)
switch sel.Sel.Name {
case "MustSprite":
art, err := strconv.Unquote(call.Args[0].(*ast.BasicLit).Value)
if err != nil {
t.Fatal(err)
}
return pixel.MustSprite(art)
case "Mirror":
return build(t, ds, kebab(sel.X.(*ast.Ident).Name)).Mirror()
}
t.Fatalf("%s built by %s", name, sel.Sel.Name)
return nil
}
func render(sp *pixel.Sprite, ink pixel.Slot) []byte {
return ui.Render(func(c *ui.Context) {
pixel.Use(c, pixel.Settings{Palette: pixel.SummerDay, Dot: 1})
pixel.Icon(c, sp, pixel.IconOptions{Slot: ink})
}, 12, 12, 1).Pix
}
func TestEveryIconIsListedWithItsArt(t *testing.T) {
ds := decls(t)
listed := map[string]bool{}
for _, n := range icon.All {
if listed[n.Name] {
t.Errorf("icon %q listed twice", n.Name)
}
listed[n.Name] = true
if _, ok := ds[n.Name]; !ok {
t.Fatalf("listed icon %q has no declaration", n.Name)
}
if string(render(n.Sprite, pixel.Magic)) != string(render(build(t, ds, n.Name), pixel.Magic)) {
t.Errorf("icon %q is bound to another icon's art", n.Name)
}
}
for name := range ds {
if !listed[name] {
t.Errorf("icon %q missing from All", name)
}
}
if len(icon.All) < 100 {
t.Fatalf("%d icons, want at least 100", len(icon.All))
}
}
func TestOneBitIconsTakeTheInk(t *testing.T) {
ds := decls(t)
back, ink := pixel.SummerDay.C(pixel.Back), pixel.SummerDay.C(pixel.Magic)
for _, n := range icon.All {
if ds[n.Name].file == "items.go" {
continue
}
pix, drawn := render(n.Sprite, pixel.Magic), 0
for i := 0; i < len(pix); i += 4 {
switch {
case pix[i] == back.R && pix[i+1] == back.G && pix[i+2] == back.B:
case pix[i] == ink.R && pix[i+1] == ink.G && pix[i+2] == ink.B:
drawn++
default:
t.Fatalf("%s draws a color other than the ink", n.Name)
}
}
if drawn == 0 {
t.Fatalf("%s draws nothing in the ink", n.Name)
}
}
}
func TestIconsAreDistinctTwelveByTwelve(t *testing.T) {
seen := map[string]string{}
for _, n := range icon.All {
if n.Sprite.W != 12 || n.Sprite.H != 12 || n.Sprite.Frames() != 1 {
t.Fatalf("%s is %d×%d with %d frames", n.Name, n.Sprite.W, n.Sprite.H, n.Sprite.Frames())
}
pix, back, drawn := render(n.Sprite, pixel.Ink), pixel.SummerDay.C(pixel.Back), 0
for i := 0; i < len(pix); i += 4 {
if pix[i] != back.R || pix[i+1] != back.G || pix[i+2] != back.B {
drawn++
}
}
if drawn == 0 {
t.Fatalf("%s draws nothing", n.Name)
}
key := string(pix)
if other, ok := seen[key]; ok {
t.Fatalf("%s and %s are the same picture", n.Name, other)
}
seen[key] = n.Name
}
}
func TestItemsAreOutlined(t *testing.T) {
for name, d := range decls(t) {
if d.file != "items.go" {
continue
}
art, _ := strconv.Unquote(d.expr.(*ast.CallExpr).Args[0].(*ast.BasicLit).Value)
var rows []string
for _, r := range strings.Split(strings.TrimSpace(art), "\n") {
rows = append(rows, strings.TrimSpace(r))
}
for y, row := range rows {
for x := range len(row) {
if strings.IndexByte(".e#", row[x]) >= 0 {
continue
}
for _, n := range [][2]int{{x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}} {
if n[0] < 0 || n[1] < 0 || n[0] >= len(row) || n[1] >= len(rows) || rows[n[1]][n[0]] == '.' {
t.Fatalf("%s: dot %d,%d touches the outside without an Edge outline", name, x, y)
}
}
}
}
}
}