package day20 import ( "fmt" "regexp" "slices" "strings" ) type PulseType int func (pt PulseType)String() string { types := []string{"high", "low"} return types[pt] } const ( HighPulse PulseType = iota LowPulse ) type Signal struct { To, From string PulseType PulseType } type Module interface { Receive(s Signal) []Signal Outputs() []string StateSnapshot() string MermaidFlow() string } // Modules type FlipFlop struct { Name string OutputNames []string IsOn bool } // ignores HighPulse // on LowPulse - toggle state and send signal func (ff *FlipFlop)Receive(s Signal) []Signal { if s.PulseType == HighPulse { return []Signal{} } ff.IsOn = !ff.IsOn outTemplate := Signal{ From: ff.Name, } if ff.IsOn { outTemplate.PulseType = HighPulse } else { outTemplate.PulseType = LowPulse } result := make([]Signal, len(ff.OutputNames)) for i, outName := range ff.OutputNames { out := outTemplate out.To = outName result[i] = out } return result } func (ff *FlipFlop)Outputs() []string { return ff.OutputNames } func (ff *FlipFlop)String() string { return fmt.Sprintf("[flip-flop '%s' (on: %t) -> %s]", ff.Name, ff.IsOn, ff.OutputNames) } func (ff *FlipFlop)StateSnapshot() string { return ff.String() } func (ff *FlipFlop)MermaidFlow() string { result := "\n" for _, toName := range ff.OutputNames { result += fmt.Sprintf("%s --> %s\n", ff.Name, toName) } return result } func IsLineFlipFlop(line string) bool { return strings.HasPrefix(line, "%") } func ParseFlipFlop(line string) (result FlipFlop) { re := regexp.MustCompile(`%(?P\D+) -> (?P.+)`) matches := re.FindStringSubmatch(line) // log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2]) result.Name = matches[1] result.OutputNames = strings.Split(matches[2], ", ") return } type Broadcast struct { OutputNames []string } // send same pulse to all outputs func (b *Broadcast)Receive(s Signal) (result []Signal) { signalTemplate := Signal{From: "broadcast", PulseType: s.PulseType} for _, out := range b.OutputNames { outSignal := signalTemplate outSignal.To = out result = append(result, outSignal) } return } func (b *Broadcast)Outputs() []string { return b.OutputNames } func (b *Broadcast)String() string { return fmt.Sprintf("[broadcast -> %+v]", b.OutputNames) } func (b *Broadcast)StateSnapshot() string { return b.String() } func (b *Broadcast)MermaidFlow() string { result := "\n" for _, toName := range b.OutputNames { result += fmt.Sprintf("%s --> %s\n", "broadcast", toName) } return result } func IsLineBroadcast(line string) bool { return strings.HasPrefix(line, "broadcaster") } func ParseBroadcast(line string) (result Broadcast) { re := regexp.MustCompile(`broadcaster -> (?P.+)`) matches := re.FindStringSubmatch(line) result.OutputNames = strings.Split(matches[1], ", ") return } type Conjunction struct { Name string OutputNames []string MostRecentPulseFromInputIsHigh map[string]bool } // remembers last signal type from all inputs (initial default is Low) // when receiving pulse, first update memory for that input // then if for all inputs remembered is high - send LowPulse // otherwise if some remembers are low - send HighPulse func (c *Conjunction)Receive(s Signal) (result []Signal) { c.MostRecentPulseFromInputIsHigh[s.From] = s.PulseType == HighPulse allHigh := true for _, latestImpulseHight := range c.MostRecentPulseFromInputIsHigh { if !latestImpulseHight { allHigh = false break } } outTemplate := Signal{From: c.Name} if allHigh { outTemplate.PulseType = LowPulse } else { outTemplate.PulseType = HighPulse } for _, outName := range c.OutputNames { outSignal := outTemplate outSignal.To = outName result = append(result, outSignal) } return } func (c *Conjunction)Outputs() []string { return c.OutputNames } func (c *Conjunction)RegisterInputs(allModules map[string]Module) { for name, module := range allModules { if slices.Contains( module.Outputs(), c.Name) { c.MostRecentPulseFromInputIsHigh[name] = false } } } func (c *Conjunction)String() string { return fmt.Sprintf("[conjunction '%s' -> %+v]", c.Name, c.OutputNames) } func (c *Conjunction)StateSnapshot() string { return fmt.Sprintf("[conjunction '%s' -> %+v]", c.Name, c.MostRecentPulseFromInputIsHigh) } func (c *Conjunction)MermaidFlow() string { result := "\n" for _, toName := range c.OutputNames { result += fmt.Sprintf("%s --> %s\n", c.Name, toName) } return result } func IsLineConjunction(line string) bool { return strings.HasPrefix(line, "&") } func ParseConjunction(line string) (result Conjunction) { re := regexp.MustCompile(`&(?P\D+) -> (?P.+)`) matches := re.FindStringSubmatch(line) // log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2]) result.Name = matches[1] result.OutputNames = strings.Split(matches[2], ", ") result.MostRecentPulseFromInputIsHigh = map[string]bool{} return } type Button struct {} func (b *Button)Receive(s Signal) []Signal { return []Signal{ { To: "broadcast", From: "button", PulseType: LowPulse }, } } func (b *Button)Outputs() []string { return []string{"broadcast"} } func (b *Button)String() string { return "[button]" } func (b *Button)StateSnapshot() string { return b.String() } func (b *Button)MermaidFlow() string { return "button --> broadcast\n" } type Output struct {} func (o *Output)Receive(s Signal) []Signal { // log.Print("Outut received signal: ", s) return []Signal{} } func (o *Output)Outputs() []string { return []string{} } func (o *Output)String() string { return "[output]" } func (o *Output)StateSnapshot() string { return o.String() } func (o *Output)MermaidFlow() string { return "" }