diff --git a/controller/types.go b/controller/types.go index b6889a3..05fc6b5 100644 --- a/controller/types.go +++ b/controller/types.go @@ -1,5 +1,7 @@ package controller +import "gitea.ligthert.net/golang/sudoku-funpark/outputter" + // Simple interface to store values shared amongst packages. type Controller struct { // All possible blocks/rows available @@ -32,4 +34,8 @@ type Controller struct { Part int // Type of output requested Output string + // Select printing method + Print string + // Outputter package + Outputter *outputter.Outputter } diff --git a/export/renderJSON.go b/export/renderJSON.go index 101f497..d9eb58d 100644 --- a/export/renderJSON.go +++ b/export/renderJSON.go @@ -2,7 +2,6 @@ package export import ( "encoding/json" - "log" ) // Render JSON output. @@ -27,8 +26,8 @@ func (export *Export) renderJSON() (render string) { } renderBytes, err := json.Marshal(solutions) if err != nil { - log.Println("ERROR: json.Marshal error:", err) - log.Println("Printing solution as-is:", solutions) + export.Controller.Outputter.Println("ERROR: json.Marshal error:", err) + export.Controller.Outputter.Println("Printing solution as-is:", solutions) return "" } render = string(renderBytes) diff --git a/flags/ParseFlags.go b/flags/ParseFlags.go index 9759851..cde9de9 100644 --- a/flags/ParseFlags.go +++ b/flags/ParseFlags.go @@ -2,7 +2,7 @@ package flags import ( "flag" - "log" + "fmt" "os" "runtime" "strings" @@ -25,13 +25,14 @@ func (flags *Flags) ParseFlags() { flag.IntVar(&flags.Controller.Split, "split", 1, "Split the tasks in n parts. This depends on the availability of the first row.") flag.IntVar(&flags.Controller.Part, "part", 1, "Process part x in n parts. Cannot be lower than 1, or higher than specified in split.") flag.StringVar(&flags.Controller.Output, "output", "human", "Type of output. 'human' for human readable. 'flat' for flat as stored in memory output. 'json' for JSON output.") + flag.StringVar(&flags.Controller.Print, "print", "short", "'short': normal output;'long': normal output with timestamps; 'silent': Only print the results;") // Parse the flags flag.Parse() // Process any changes to the CPU usage. if flags.Controller.NumCPUs <= 0 { - log.Printf("ERROR: Number of CPU cores must be 1 or higher.\n\n") + fmt.Printf("ERROR: Number of CPU cores must be 1 or higher.\n\n") flags.printUsage() os.Exit(1) } @@ -42,7 +43,7 @@ func (flags *Flags) ParseFlags() { // Process rows if flags.Controller.Row1 == "000000000" || flags.Controller.Row2 == "000000000" || flags.Controller.Row3 == "000000000" || flags.Controller.Row4 == "000000000" || flags.Controller.Row5 == "000000000" || flags.Controller.Row6 == "000000000" || flags.Controller.Row7 == "000000000" || flags.Controller.Row8 == "000000000" || flags.Controller.Row9 == "000000000" { - log.Printf("ERROR: All parameters must be entered.\n\n") + fmt.Printf("ERROR: All parameters must be entered.\n\n") flags.printUsage() os.Exit(1) } @@ -61,14 +62,14 @@ func (flags *Flags) ParseFlags() { // Process workload splitting // Ensure split and part are 1 or higher if flags.Controller.Split <= 0 || flags.Controller.Part <= 0 { - log.Printf("ERROR: '-split' and '-part' need to be 1 or higher.\n") + fmt.Printf("ERROR: '-split' and '-part' need to be 1 or higher.\n") flags.printUsage() os.Exit(1) } // Ensure part is between 1 and split if flags.Controller.Part > flags.Controller.Split { - log.Printf("ERROR: '-part' cannot be bigger than `-split`.\n") + fmt.Printf("ERROR: '-part' cannot be bigger than `-split`.\n") flags.printUsage() os.Exit(1) } @@ -76,7 +77,15 @@ func (flags *Flags) ParseFlags() { // Process output selection flags.Controller.Output = strings.ToLower(flags.Controller.Output) if flags.Controller.Output != "human" && flags.Controller.Output != "flat" && flags.Controller.Output != "json" { - log.Printf("ERROR: Invalid output, can only be 'human' or 'flat'.\n") + fmt.Printf("ERROR: Invalid output, can only be 'human' or 'flat' or 'json'.\n") + flags.printUsage() + os.Exit(1) + } + + // Process print selection + flags.Controller.Print = strings.ToLower(flags.Controller.Print) + if flags.Controller.Print != "short" && flags.Controller.Print != "long" && flags.Controller.Print != "silent" { + fmt.Printf("ERROR: Invalid Print, can only be 'short' or 'long' or 'silent'.\n") flags.printUsage() os.Exit(1) } diff --git a/flags/validateRow.go b/flags/validateRow.go index cdb2b2a..6f70514 100644 --- a/flags/validateRow.go +++ b/flags/validateRow.go @@ -1,7 +1,7 @@ package flags import ( - "log" + "fmt" "os" ) @@ -18,7 +18,7 @@ func (flags *Flags) validateRow(name string, row string) { // 1. Make sure the row is 9 in length if len(row) != 9 { - log.Printf("ERROR: Invalid length of %s (%s), must be 9 numbers\n\n", name, row) + fmt.Printf("ERROR: Invalid length of %s (%s), must be 9 numbers\n\n", name, row) flags.printUsage() os.Exit(1) } @@ -29,7 +29,7 @@ func (flags *Flags) validateRow(name string, row string) { } if !found { - log.Printf("ERROR: Invalid character of %s (%s), must be 9 numbers\n\n", name, row) + fmt.Printf("ERROR: Invalid character of %s (%s), must be 9 numbers\n\n", name, row) flags.printUsage() os.Exit(1) } @@ -46,7 +46,7 @@ func (flags *Flags) validateRow(name string, row string) { } if double { - log.Printf("ERROR: Double character of %s (%s), numbers between 1 and 9 may only be entered once\n\n", name, row) + fmt.Printf("ERROR: Double character of %s (%s), numbers between 1 and 9 may only be entered once\n\n", name, row) flags.printUsage() os.Exit(1) } diff --git a/main.go b/main.go index bf71599..b6c77b3 100644 --- a/main.go +++ b/main.go @@ -1,29 +1,34 @@ package main import ( - "log" + "fmt" "runtime" "strconv" "gitea.ligthert.net/golang/sudoku-funpark/controller" "gitea.ligthert.net/golang/sudoku-funpark/export" "gitea.ligthert.net/golang/sudoku-funpark/flags" + "gitea.ligthert.net/golang/sudoku-funpark/outputter" "gitea.ligthert.net/golang/sudoku-funpark/solver" ) func main() { // Instantiate the interfaces controller := controller.Controller{} + outp := outputter.Outputter{} export := export.Export{Controller: &controller} flags := flags.Flags{Controller: &controller} - solver := solver.Solver{Controller: &controller} + solver := solver.Solver{Controller: &controller, Outp: &outp} // Parse and handle flags flags.ParseFlags() + // Tell outp what kind of output is expected. + outp.OutputType = controller.Print + // Report number of CPUs being used, if set. if runtime.NumCPU() != controller.NumCPUs { - log.Println("Using " + strconv.Itoa(controller.NumCPUs) + " CPUs, (was " + strconv.Itoa(runtime.NumCPU()) + ")") + outp.Println("Using " + strconv.Itoa(controller.NumCPUs) + " CPUs, (was " + strconv.Itoa(runtime.NumCPU()) + ")") } // Load blocks from CSV file @@ -39,12 +44,12 @@ func main() { } // Print the total number of solutions to validate - log.Println("Number of (potential) solutions:", solver.Iter) + outp.Println("Number of (potential) solutions:", solver.Iter) // Check the number of solutions go solver.CheckCombinations() solver.Tracker() - log.Println(export.Export()) + fmt.Println(export.Export()) } diff --git a/outputter/Printf.go b/outputter/Printf.go new file mode 100644 index 0000000..ad840ee --- /dev/null +++ b/outputter/Printf.go @@ -0,0 +1,17 @@ +package outputter + +import ( + "fmt" + "log" +) + +func (outputter *Outputter) Printf(format string, args ...any) { + switch outputter.OutputType { + case "short": + fmt.Printf(format, args...) + case "long": + log.Printf(format, args...) + case "silent": + // Do nothing + } +} diff --git a/outputter/Println.go b/outputter/Println.go new file mode 100644 index 0000000..b65913c --- /dev/null +++ b/outputter/Println.go @@ -0,0 +1,17 @@ +package outputter + +import ( + "fmt" + "log" +) + +func (outputter *Outputter) Println(msg ...any) { + switch outputter.OutputType { + case "short": + fmt.Println(msg...) + case "long": + log.Println(msg...) + case "silent": + // Do nothing + } +} diff --git a/outputter/types.go b/outputter/types.go new file mode 100644 index 0000000..86555eb --- /dev/null +++ b/outputter/types.go @@ -0,0 +1,5 @@ +package outputter + +type Outputter struct { + OutputType string +} diff --git a/solver/LoadBlocks.go b/solver/LoadBlocks.go index 4a84571..7b11b66 100644 --- a/solver/LoadBlocks.go +++ b/solver/LoadBlocks.go @@ -2,7 +2,6 @@ package solver import ( "embed" - "log" "strings" "time" ) @@ -15,8 +14,8 @@ var f embed.FS // Load all possible blocks from CSV in to memory func (solver *Solver) LoadBlocks() { - defer solver.timeTrack(time.Now(), "Loaded blocks") - log.Println("Loading blocks") + defer solver.timeTrack(time.Now(), "Done!") + solver.Outp.Printf("Loading blocks... ") var blocks []string diff --git a/solver/PopulateBlocks.go b/solver/PopulateBlocks.go index 8c54287..0897ac7 100644 --- a/solver/PopulateBlocks.go +++ b/solver/PopulateBlocks.go @@ -1,15 +1,14 @@ package solver import ( - "log" "time" ) // Find all possible blocks that can be used to find a solution. func (solver *Solver) PopulateBlocks() { - defer solver.timeTrack(time.Now(), "Populated blocks") - log.Println("Populating blocks") + defer solver.timeTrack(time.Now(), "Done!") + solver.Outp.Printf("Populating blocks... ") solver.findBlocks(&solver.Controller.Row1, &solver.row1s) solver.findBlocks(&solver.Controller.Row2, &solver.row2s) diff --git a/solver/SelectWorkload.go b/solver/SelectWorkload.go index 40a2692..92af577 100644 --- a/solver/SelectWorkload.go +++ b/solver/SelectWorkload.go @@ -1,7 +1,6 @@ package solver import ( - "log" "os" "strconv" "time" @@ -12,12 +11,12 @@ import ( // Modify solver.row1s so it limits the workload to what is only desired func (solver *Solver) SelectWorkload() { if solver.Controller.Split > len(solver.row1s) { - log.Println("ERROR: Unable to divide the workload in " + strconv.Itoa(solver.Controller.Split) + " parts, when only " + strconv.Itoa(len(solver.row1s)) + " are available.\n\n") + solver.Outp.Println("ERROR: Unable to divide the workload in " + strconv.Itoa(solver.Controller.Split) + " parts, when only " + strconv.Itoa(len(solver.row1s)) + " are available.\n\n") os.Exit(1) } defer solver.timeTrack(time.Now(), "Workload set") - log.Println("Setting workload") - log.Println("We are agent " + strconv.Itoa(solver.Controller.Part) + " of " + strconv.Itoa(solver.Controller.Split)) + solver.Outp.Println("Setting workload") + solver.Outp.Println("We are agent " + strconv.Itoa(solver.Controller.Part) + " of " + strconv.Itoa(solver.Controller.Split)) workloads := solver.splitWorkload() solver.setWorkload(workloads) } diff --git a/solver/Tracker.go b/solver/Tracker.go index fdeca5a..c9e34c4 100644 --- a/solver/Tracker.go +++ b/solver/Tracker.go @@ -1,7 +1,6 @@ package solver import ( - "log" "strconv" "time" ) @@ -12,7 +11,7 @@ func (solver *Solver) Tracker() { // Add time tracking defer solver.timeTrack(time.Now(), "Validated solutions") - log.Println("Validating solutions") + solver.Outp.Println("Validating solutions") // Determine if the main-loop is done var done bool @@ -73,7 +72,7 @@ func (solver *Solver) Tracker() { } // Printing the progress - log.Println("Processing: " + strconv.Itoa(int(percentage)) + "% (" + strconv.FormatUint(solver.counter.Load(), 10) + "/" + strconv.Itoa(int(solver.Iter)) + "); Rate: " + strconv.FormatUint(rateDiff, 10) + "/sec for " + timer_elapsed.String() + "; Time left (est.): " + est_fin) + solver.Outp.Println("Processing: " + strconv.Itoa(int(percentage)) + "% (" + strconv.FormatUint(solver.counter.Load(), 10) + "/" + strconv.Itoa(int(solver.Iter)) + "); Rate: " + strconv.FormatUint(rateDiff, 10) + "/sec for " + timer_elapsed.String() + "; Time left (est.): " + est_fin) // After we are done printing, exit this for-loop if percentage == 100 { diff --git a/solver/timeTrack.go b/solver/timeTrack.go index bc77f7e..ede9d04 100644 --- a/solver/timeTrack.go +++ b/solver/timeTrack.go @@ -1,7 +1,6 @@ package solver import ( - "log" "time" ) @@ -9,5 +8,5 @@ import ( // Use with `defer` func (solver *Solver) timeTrack(start time.Time, msg string) { elapsed := time.Since(start) - log.Printf("%s (%s)", msg, elapsed) + solver.Outp.Printf("%s (%s)\n", msg, elapsed) } diff --git a/solver/types.go b/solver/types.go index bb428fa..5b0bef3 100644 --- a/solver/types.go +++ b/solver/types.go @@ -4,6 +4,7 @@ import ( "sync/atomic" "gitea.ligthert.net/golang/sudoku-funpark/controller" + "gitea.ligthert.net/golang/sudoku-funpark/outputter" ) // Solve a given Sudoku puzzle by iterating through all possible solutions. @@ -33,4 +34,6 @@ type Solver struct { counter atomic.Uint64 // Slice of rates for accurate duration estimation. rates []uint64 + // Reference to Outputter interface + Outp *outputter.Outputter }