Pinball dot-matrix clock animation thingy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

199 lines
4.3 KiB

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)
}
}