package matrix
|
|
|
|
import (
|
|
"log"
|
|
"math/rand"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"maze.io/x/dmd"
|
|
)
|
|
|
|
const minimalDuration = time.Second / 3
|
|
|
|
var defaultLoaderConfig = &LoaderConfig{
|
|
Paths: []string{"."},
|
|
Database: "loader.db",
|
|
}
|
|
|
|
type LoaderConfig struct {
|
|
Paths []string `toml:"paths"`
|
|
Database string `toml:"database"'`
|
|
}
|
|
|
|
type Loader struct {
|
|
BySet map[string]map[string]dmd.Animation
|
|
ByName map[string]dmd.Animation
|
|
Names []string
|
|
}
|
|
|
|
func (l *Loader) loadAll(pattern string, loader func(name string) (dmd.Animations, error), namer func(string) string) error {
|
|
log.Println("loader: load all", pattern)
|
|
names, err := filepath.Glob(pattern)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range names {
|
|
set, err := loader(file)
|
|
if err != nil {
|
|
log.Printf("loader: %s is corrupt: %v", file, err)
|
|
continue
|
|
}
|
|
|
|
base := namer(file)
|
|
if _, ok := l.BySet[base]; !ok {
|
|
log.Printf("loader: new set %q", base)
|
|
l.BySet[base] = make(map[string]dmd.Animation)
|
|
}
|
|
|
|
for _, a := range set {
|
|
if a.Len() <= 2 {
|
|
continue
|
|
}
|
|
if a.Duration() <= minimalDuration {
|
|
continue
|
|
}
|
|
name := strings.ToLower(a.Name())
|
|
if name == "" {
|
|
continue
|
|
}
|
|
l.BySet[base][name] = a
|
|
if _, exists := l.ByName[name]; !exists {
|
|
l.ByName[name] = a
|
|
l.Names = append(l.Names, name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewLoader(config *LoaderConfig) (*Loader, error) {
|
|
l := &Loader{
|
|
BySet: make(map[string]map[string]dmd.Animation),
|
|
ByName: make(map[string]dmd.Animation),
|
|
}
|
|
|
|
for _, path := range config.Paths {
|
|
if !filepath.IsAbs(path) {
|
|
var err error
|
|
if path, err = filepath.Abs(expandPath(path)); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
log.Printf("loader: from %s\n", path)
|
|
if err := l.loadAll(filepath.Join(path, "*.[dD][mM][dD]"), loadDMD1, nameFromFile); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := l.loadAll(filepath.Join(path, "*.[aA][nN][iI]"), loadVPIN, nameFromFile); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := l.loadAll(filepath.Join(path, "*.[vV][nN][iI]"), loadVPIN, nameFromFile); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := l.loadAll(filepath.Join(path, "*.[rR][aA][wW]"), loadRunDMD, nameFromFile); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := l.loadAll(filepath.Join(path, "*.[gG][iI][fF]"), loadGIF, nameFromFile); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if l := len(l.Names); l == 0 {
|
|
log.Println("loader: no animations were loaded")
|
|
} else {
|
|
log.Printf("loader: %d animations found\n", l)
|
|
}
|
|
|
|
return l, nil
|
|
}
|
|
|
|
func expandPath(path string) string {
|
|
if len(path) > 0 && strings.HasPrefix(path, "~/") {
|
|
u, err := user.Current()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return filepath.Join(u.HomeDir, path[2:])
|
|
}
|
|
return path
|
|
}
|
|
|
|
func loadDMD1(name string) (dmd.Animations, error) {
|
|
f, err := os.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a, err := dmd.DecodeDMD1(f)
|
|
if err != nil {
|
|
_ = f.Close()
|
|
log.Printf("loader: %s is corrupt: %v", name, err)
|
|
return nil, err
|
|
} else if err = f.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
return dmd.Animations{a}, nil
|
|
}
|
|
|
|
func loadRunDMD(name string) (dmd.Animations, error) {
|
|
f, err := os.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// defer func() { _ = f.Close() }()
|
|
return dmd.DecodeRunDMD(f)
|
|
}
|
|
|
|
func loadVPIN(name string) (dmd.Animations, error) {
|
|
paletteName := name[:len(name)-len(filepath.Ext(name))] + ".pal"
|
|
c, err := dmd.LoadPin2DMDColoring(paletteName)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
|
|
f, err := os.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
return dmd.DecodeVPINWithColoring(f, c)
|
|
}
|
|
|
|
func loadGIF(name string) (dmd.Animations, error) {
|
|
f, err := os.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
a, err := dmd.DecodeGIF(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return dmd.Animations{a}, nil
|
|
}
|
|
|
|
func nameFromFile(name string) string {
|
|
base := filepath.Base(name)
|
|
return base[:len(base)-len(filepath.Ext(base))]
|
|
}
|
|
|
|
func nameFromDir(name string) string {
|
|
return filepath.Base(filepath.Dir(name))
|
|
}
|
|
|
|
func (l *Loader) RenderRandomTo(d *Display) {
|
|
for {
|
|
var (
|
|
name = l.Names[rand.Intn(len(l.Names))]
|
|
a = l.ByName[name]
|
|
)
|
|
if err := d.RenderAnimation(a); err != nil {
|
|
log.Printf("loader: display error: %v", err)
|
|
}
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
}
|