moth/cmd/transpile/category.go

156 lines
3.3 KiB
Go
Raw Normal View History

2019-08-17 16:00:15 -06:00
package main
import (
"os"
"flag"
"fmt"
2019-08-18 21:59:06 -06:00
"log"
"path/filepath"
"hash/fnv"
"encoding/binary"
"encoding/json"
"encoding/hex"
"strconv"
"math/rand"
"context"
"time"
"os/exec"
"bytes"
2019-08-17 16:00:15 -06:00
)
2019-08-18 21:59:06 -06:00
type PuzzleEntry struct {
Id string
Points int
Puzzle Puzzle
}
func PrngOfStrings(input ...string) (*rand.Rand) {
hasher := fnv.New64()
for _, s := range input {
fmt.Fprint(hasher, s, "\n")
}
seed := binary.BigEndian.Uint64(hasher.Sum(nil))
source := rand.NewSource(int64(seed))
return rand.New(source)
}
func runPuzzleGen(puzzlePath string, seed string) (*Puzzle, error) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
cmd := exec.CommandContext(ctx, puzzlePath)
cmd.Env = append(
os.Environ(),
fmt.Sprintf("MOTH_PUZZLE_SEED=%s", seed),
)
stdout, err := cmd.Output()
2019-08-18 21:59:06 -06:00
if err != nil {
return nil, err
}
jsdec := json.NewDecoder(bytes.NewReader(stdout))
jsdec.DisallowUnknownFields()
puzzle := new(Puzzle)
err = jsdec.Decode(puzzle)
2019-08-18 21:59:06 -06:00
if err != nil {
return nil, err
}
2019-08-18 21:59:06 -06:00
return puzzle, nil
}
func ParseCategory(categoryPath string, seed string) ([]PuzzleEntry, error) {
categoryFd, err := os.Open(categoryPath)
if err != nil {
return nil, err
}
defer categoryFd.Close()
puzzleDirs, err := categoryFd.Readdirnames(0)
if err != nil {
return nil, err
}
puzzleEntries := make([]PuzzleEntry, 0, len(puzzleDirs))
for _, puzzleDir := range puzzleDirs {
var puzzle *Puzzle
2019-08-18 21:59:06 -06:00
puzzlePath := filepath.Join(categoryPath, puzzleDir)
// Determine point value from directory name
2019-08-18 21:59:06 -06:00
points, err := strconv.Atoi(puzzleDir)
if err != nil {
log.Printf("Skipping %s: %v", puzzlePath, err)
continue
}
// Try the .moth file first
puzzleMothPath := filepath.Join(puzzlePath, "puzzle.moth")
puzzleFd, err := os.Open(puzzleMothPath)
if err == nil {
defer puzzleFd.Close()
puzzle, err = ParseMoth(puzzleFd)
if err != nil {
log.Printf("Skipping %s: %v", puzzleMothPath, err)
continue
}
} else if os.IsNotExist(err) {
var genErr error
puzzleGenPath := filepath.Join(puzzlePath, "mkpuzzle")
puzzle, genErr = runPuzzleGen(puzzleGenPath, puzzlePath)
if genErr != nil {
log.Printf("Skipping %20s: %v", puzzleMothPath, err)
log.Printf("Skipping %20s: %v", puzzleGenPath, genErr)
continue
}
} else {
log.Printf("Skipping %s: %v", puzzleMothPath, err)
2019-08-18 21:59:06 -06:00
continue
}
// Create a category entry for this
prng := PrngOfStrings(puzzlePath)
2019-08-18 21:59:06 -06:00
idBytes := make([]byte, 16)
prng.Read(idBytes)
id := hex.EncodeToString(idBytes)
puzzleEntry := PuzzleEntry{
Id: id,
Puzzle: *puzzle,
Points: points,
}
puzzleEntries = append(puzzleEntries, puzzleEntry)
}
return puzzleEntries, nil
}
2019-08-17 16:00:15 -06:00
func main() {
2019-08-18 21:59:06 -06:00
// XXX: We need a way to pass in "only run this one point value puzzle"
2019-08-18 21:59:59 -06:00
// XXX: Convert puzzle.py to standalone thingies
2019-08-17 16:00:15 -06:00
flag.Parse()
2019-08-18 21:59:06 -06:00
baseSeedString := os.Getenv("SEED")
2019-08-17 16:00:15 -06:00
jsenc := json.NewEncoder(os.Stdout)
jsenc.SetEscapeHTML(false)
jsenc.SetIndent("", " ")
2019-08-17 16:00:15 -06:00
for _, dirname := range flag.Args() {
2019-08-18 21:59:06 -06:00
categoryName := filepath.Base(dirname)
categorySeed := fmt.Sprintf("%s/%s", baseSeedString, categoryName)
puzzles, err := ParseCategory(dirname, categorySeed)
if err != nil {
log.Print(err)
continue
}
if err := jsenc.Encode(puzzles); err != nil {
2019-08-18 21:59:06 -06:00
log.Print(err)
continue
}
2019-08-17 16:00:15 -06:00
}
2019-08-18 21:59:06 -06:00
}