Advent-of-Code-2023/day20/modules.go

244 lines
5.0 KiB
Go

package day20
import (
"fmt"
"log"
"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
}
// 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 IsLineFlipFlop(line string) bool {
return strings.HasPrefix(line, "%")
}
func ParseFlipFlop(line string) (result FlipFlop) {
re := regexp.MustCompile(`%(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`)
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 IsLineBroadcast(line string) bool {
return strings.HasPrefix(line, "broadcaster")
}
func ParseBroadcast(line string) (result Broadcast) {
re := regexp.MustCompile(`broadcaster -> (?P<OUTPUTS>.+)`)
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 IsLineConjunction(line string) bool {
return strings.HasPrefix(line, "&")
}
func ParseConjunction(line string) (result Conjunction) {
re := regexp.MustCompile(`&(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`)
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()
}
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()
}