diff --git a/go.mod b/go.mod index 100cb06..ee1de1f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module maze-solver go 1.21 require ( + github.com/akamensky/argparse v1.4.0 github.com/mazznoer/colorgrad v0.9.1 golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b ) diff --git a/go.sum b/go.sum index fa99ae0..f53cbd7 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc= +github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mazznoer/colorgrad v0.9.1 h1:MB80JYVndKWSMEM1beNqnuOowWGhoQc3DXWXkFp6JlM= diff --git a/io/reader/reader.go b/io/reader/reader.go index e42e293..f3c3c31 100644 --- a/io/reader/reader.go +++ b/io/reader/reader.go @@ -1,5 +1,48 @@ package reader +import ( + "fmt" + "image/color" +) + type Reader interface { Read() (*RawMaze, error) } + +type ReaderFactory struct { + Type string + Filename *string + PathChar, WallChar, SolutionChar *string + CellWidth, CellHeight *int + WallColor, PathColor color.Color +} + +const ( + _IMAGE = "image" + _TEXT = "text" +) + +var TYPES = map[string]string{ + ".png": _IMAGE, + ".txt": _TEXT, +} + +func (f *ReaderFactory) Get() Reader { + switch f.Type { + case _TEXT: + return &TextReader{ + Filename: *f.Filename, + PathChar: byte((*f.PathChar)[0]), + WallChar: byte((*f.WallChar)[0]), + } + case _IMAGE: + return &ImageReader{ + Filename: *f.Filename, + CellWidth: *f.CellWidth, + CellHeight: *f.CellHeight, + WallColor: f.WallColor, + PathColor: f.PathColor, + } + } + panic(fmt.Sprintf("Unrecognized reader type %q", f.Type)) +} diff --git a/io/reader/text.go b/io/reader/text.go index 70e244d..faa4f8e 100644 --- a/io/reader/text.go +++ b/io/reader/text.go @@ -11,7 +11,7 @@ type TextReader struct { PathChar, WallChar byte } -func (r TextReader) Read() (*RawMaze, error) { +func (r *TextReader) Read() (*RawMaze, error) { defer utils.Timer("Text Reader", 3)() lines, err := getLines(r.Filename) if err != nil { diff --git a/io/writer/writer.go b/io/writer/writer.go index cf68968..2b5ac5b 100644 --- a/io/writer/writer.go +++ b/io/writer/writer.go @@ -1,5 +1,46 @@ package writer +import ( + "fmt" + "image/color" + "maze-solver/maze" + + "github.com/mazznoer/colorgrad" +) + type Writer interface { Write() error } + +type WriterFactory struct { + Type string + Filename *string + PathChar, WallChar, SolutionChar *string + CellWidth, CellHeight *int + WallColor, PathColor color.Color + SolutionGradient colorgrad.Gradient +} + +const ( + _IMAGE = "image" +) + +var TYPES = map[string]string{ + ".png": _IMAGE, +} + +func (f *WriterFactory) Get(m *maze.SolvedMaze) Writer { + switch f.Type { + case _IMAGE: + return &ImageWriter{ + Filename: *f.Filename, + Maze: m, + CellWidth: *f.CellWidth, + CellHeight: *f.CellHeight, + WallColor: f.WallColor, + PathColor: f.PathColor, + SolutionGradient: f.SolutionGradient, + } + } + panic(fmt.Sprintf("Unrecognized writer type %q", f.Type)) +} diff --git a/main.go b/main.go index fc67d42..75eaa85 100644 --- a/main.go +++ b/main.go @@ -1,25 +1,154 @@ package main import ( + "errors" + "fmt" + "image/color" "maze-solver/io/reader" "maze-solver/io/writer" "maze-solver/maze/parser" "maze-solver/solver" "maze-solver/utils" + "os" + "strings" + + "github.com/akamensky/argparse" + "github.com/mazznoer/colorgrad" ) func main() { - output := "filename" + argparser := argparse.NewParser("maze-solver", "Solves the given maze (insane, right? who would've guessed?)") - reader := &reader.TextReader{Filename: "filename", PathChar: ' ', WallChar: '#'} - writer := &writer.ImageWriter{} + var verboseLevel *int = argparser.FlagCounter("v", "verbose", &argparse.Options{ + Help: `Verbose level of the solver + 0: nothing printed to stdout + 1: print the total time taken by the solver (time of the main() function) + 2: prints the time the solving algorithm took to run + 3: prints the time taken by each section (reader, solving algorithm, writer)`, + }) - solver := &solver.Bfs{} + readerFactory := reader.ReaderFactory{} + writerFactory := writer.WriterFactory{} + solverFactory := solver.SolverFactory{} + + readerFactory.Type = reader.TYPES[".png"] + readerFactory.Filename = argparser.String("i", "input", &argparse.Options{ + Help: "Input file", + Default: "maze.png", + Validate: func(args []string) error { + var ok bool + extension := args[0][len(args[0])-4:] + readerFactory.Type, ok = reader.TYPES[extension] + if ok { + return nil + } else { + return errors.New(fmt.Sprintf("Filetype not recognized %q", extension)) + } + }, + }) + + writerFactory.Type = writer.TYPES[".png"] + writerFactory.Filename = argparser.String("o", "output", &argparse.Options{ + Help: "Input file", + Default: "maze_sol.png", + Validate: func(args []string) error { + var ok bool + extension := args[0][len(args[0])-4:] + writerFactory.Type, ok = writer.TYPES[extension] + if ok { + return nil + } else { + return errors.New(fmt.Sprintf("Filetype not recognized %q", extension)) + } + }, + }) + + readerFactory.PathChar = argparser.String("", "path-char-in", &argparse.Options{ + Help: "Character to represent the path in a input text file", + Default: " ", + Validate: func(args []string) error { + if len(args[0]) > 1 { + return errors.New("Character must a string of length 1") + } + return nil + }, + }) + + readerFactory.WallChar = argparser.String("", "wall-char-in", &argparse.Options{ + Help: "Character to represent the wall in a input text file", + Default: "#", + Validate: func(args []string) error { + if len(args[0]) > 1 { + return errors.New("Character must a string of length 1") + } + return nil + }, + }) + + writerFactory.PathChar = argparser.String("", "path-char-out", &argparse.Options{ + Help: "Character to represent the path in a output text file", + Default: " ", + Validate: func(args []string) error { + if len(args[0]) > 1 { + return errors.New("Character must a string of length 1") + } + return nil + }, + }) + + writerFactory.WallChar = argparser.String("", "wall-char-out", &argparse.Options{ + Help: "Character to represent the wall in a output text file", + Default: "#", + Validate: func(args []string) error { + if len(args[0]) > 1 { + return errors.New("Character must a string of length 1") + } + return nil + }, + }) + + cellSizeIn := argparser.Int("", "cell-size-in", &argparse.Options{ + Help: "Size of a cell (in pixels) for input file of image type", + Default: 20, + }) + + cellSizeOut := argparser.Int("", "cell-size-out", &argparse.Options{ + Help: "Size of a cell (in pixels) for output file of image type", + Default: 20, + }) + + solverFactory.Type = argparser.Selector("a", "algo", solver.TYPES, &argparse.Options{ + Help: fmt.Sprintf("Algorithm to solve the maze, avaiable options: %s", strings.Join(solver.TYPES, ", ")), + Default: solver.TYPES[0], + }) + + if err := argparser.Parse(os.Args); err != nil { + fmt.Println(argparser.Usage(err)) + return + } + utils.VERBOSE_LEVEL = *verboseLevel + + readerFactory.CellHeight, readerFactory.CellWidth = cellSizeIn, cellSizeIn + readerFactory.WallColor = color.RGBA{0, 0, 0, 255} + readerFactory.PathColor = color.RGBA{255, 255, 255, 255} + + writerFactory.CellHeight, writerFactory.CellWidth = cellSizeOut, cellSizeOut + writerFactory.WallColor = color.RGBA{0, 0, 0, 255} + writerFactory.PathColor = color.RGBA{255, 255, 255, 255} + writerFactory.SolutionGradient = colorgrad.Warm() + + defer utils.Timer("TOTAL", 1)() + + reader := readerFactory.Get() maze, err := parser.Parse(reader) - utils.Check(err, "Couldn't read maze from %q", reader.Filename) + utils.Check(err, "Couldn't read maze") + solver := solverFactory.Get() solved := solver.Solve(maze) - err = writer.Write(output, solved) - utils.Check(err, "Couldn't write solved maze to %q", output) + + writer := writerFactory.Get(solved) + + err = writer.Write() + utils.Check(err, "Couldn't write solved maze") } diff --git a/solver/solver.go b/solver/solver.go index 0dd6d35..cc59d30 100644 --- a/solver/solver.go +++ b/solver/solver.go @@ -1,7 +1,30 @@ package solver -import "maze-solver/maze" +import ( + "fmt" + "maze-solver/maze" +) type Solver interface { Solve(*maze.Maze) *maze.SolvedMaze } + +type SolverFactory struct { + Type *string +} + +const ( + _TURN_LEFT = "turn-left" +) + +var TYPES = []string{ + _TURN_LEFT, +} + +func (f *SolverFactory) Get() Solver { + switch *f.Type { + case _TURN_LEFT: + return &TurnLeftSolver{} + } + panic(fmt.Sprintf("Unrecognized solver type %q", *f.Type)) +}