package challenge import ( "bufio" "encoding/json" "errors" "fmt" "io" "strconv" "strings" ) func readConfig(r io.Reader) (Config, error) { var cfg Config if err := json.NewDecoder(r).Decode(&cfg); err != nil { return cfg, err } return cfg, nil } func readEvents(r io.Reader) ([]Event, error) { var events []Event scanner := bufio.NewScanner(r) lineNo := 0 for scanner.Scan() { lineNo++ line := strings.TrimSpace(scanner.Text()) if line == "" { continue } event, err := parseEvent(line) if err != nil { return nil, fmt.Errorf("line %d: %w", lineNo, err) } events = append(events, event) } return events, scanner.Err() } func parseEvent(line string) (Event, error) { fields := strings.Fields(line) if len(fields) < 3 { return Event{}, errors.New("not enough fields") } if len(fields[0]) != len("[15:04:05]") || fields[0][0] != '[' || fields[0][len(fields[0])-1] != ']' { return Event{}, errors.New("bad time format") } at, err := parseClock(fields[0][1 : len(fields[0])-1]) if err != nil { return Event{}, err } player, err := strconv.Atoi(fields[1]) if err != nil { return Event{}, err } id, err := strconv.Atoi(fields[2]) if err != nil { return Event{}, err } return Event{At: at, Player: player, ID: id, Extra: strings.Join(fields[3:], " ")}, nil } func parseNonNegativeInt(value string) (int, bool) { if strings.TrimSpace(value) != value || value == "" || strings.Contains(value, " ") { return 0, false } n, err := strconv.Atoi(value) return n, err == nil && n >= 0 }