diff --git a/day-4/main.go b/day-4/main.go new file mode 100644 index 0000000..d24ba2a --- /dev/null +++ b/day-4/main.go @@ -0,0 +1,271 @@ +/* Day 4: Giant Squid */ +// TODO: Redo this whole thing with bit arrays instead +package main + +import ( + "AoC2021/utils/timer" + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +func check(e error) { + if e != nil { + fmt.Println("Oh snap!") + panic(e) + } +} + +func main() { + fmt.Println("--- Day 4: Giant Squid ---") + timer.Start() + part1() + timer.Tick() + fmt.Println() + + part2() + timer.Tick() + fmt.Println() +} + +type Card = [][]string + +func readData(input *os.File) ([]string, []Card) { + scanner := bufio.NewScanner(input) + + var draws []string + + if scanner.Scan() { + draws = strings.Split(scanner.Text(), ",") + } + + var cards []Card + cardIdx := -1 // the first new line will initialize card 0 + rowIdx := 0 + + // fmt.Printf("Created cards %s", cards) + for scanner.Scan() { + line := scanner.Text() + switch { + case line == "": // when we hit an epty line, initialize a new card + cards = append(cards, Card{}) + cardIdx += 1 + rowIdx = 0 + //fmt.Printf("Added card %d to cards {%s}\n", cardIdx, cards) + default: + rowVals := strings.Fields(line) + //fmt.Printf("Attempting to push [%s] to card %d row %d {%s}\n", line, cardIdx, rowIdx, cards[cardIdx]) + cards[cardIdx] = append(cards[cardIdx], rowVals) + rowIdx += 1 + } + } + + return draws, cards +} + +type MarkedCard = [][]bool + +func initializeMarks(cards []Card) []MarkedCard { + // create a number of marked cards equal to the number of bingo cards + cardsLen := len(cards) + marks := make([]MarkedCard, cardsLen) + // find the number of rows on a card + rowCount := len(cards[0]) + colCount := len(cards[0][0]) + + for cIdx := 0; cIdx < cardsLen; cIdx++ { + marks[cIdx] = make([][]bool, rowCount) + for rIdx := 0; rIdx < rowCount; rIdx++ { + //fmt.Printf("Attempting to add row %d to card %d", rIdx, cIdx) + //fmt.Println(marks[cIdx]) + marks[cIdx][rIdx] = make([]bool, colCount) + } + } + + return marks +} + +func markCards(cards []Card, marks []MarkedCard, draw string) []MarkedCard { + for cIdx, card := range cards { + for rowIdx, row := range card { + for colIdx, value := range row { + if value == draw { + marks[cIdx][rowIdx][colIdx] = true + } + } + } + } + + return marks +} + +// TODO: currently assumes only one card will win per round +func findWinningCardIdx(marks []MarkedCard) int { + for cIdx, card := range marks { + if matchesRow(card) || matchesColumn(card) { + return cIdx + } + } + + return -1 +} + +func matchesRow(card MarkedCard) bool { + for _, row := range card { + marks := 0 + for _, value := range row { + if value { + marks += 1 + } + } + if marks == len(row) { + return true + } + } + return false +} + +func matchesColumn(card MarkedCard) bool { + rowCount := len(card) + colCount := len(card[0]) + for colIdx := 0; colIdx < colCount; colIdx++ { + marks := 0 + for rowIdx := 0; rowIdx < rowCount; rowIdx++ { + if card[rowIdx][colIdx] { + marks += 1 + } + } + + if marks == colCount { + return true + } + } + return false +} + +func getCardScore(card Card, mark MarkedCard) int { + score := 0 + + // sum up all the non-marked bingo spots + for rIdx, row := range card { + for cIdx, strVal := range row { + if !mark[rIdx][cIdx] { + value, err := strconv.Atoi(strVal) + check(err) + + score += value + } + } + } + return score +} + +func part1() { + fmt.Println("* Part 1 *") + fmt.Println(" Goal: Figure out which bingo board will win first.") + fmt.Println(" Answer: What will your final score be if you choose that board?") + + input, err := os.Open("./input.txt") + check(err) + defer input.Close() + + draws, cards := readData(input) + marks := initializeMarks(cards) + + //fmt.Printf("Read in %d draws and %d cards\n", len(draws), len(cards)) + //fmt.Printf("Initialized %d mark cards\n", len(marks)) + //fmt.Printf("First card %s\n", cards[0]) + //fmt.Println(marks[0]) + + drawTimes := 0 + winningCardIdx := -1 + lastDraw := 0 + score := 0 + for _, draw := range draws { + drawTimes += 1 + marks = markCards(cards, marks, draw) + winningCardIdx = findWinningCardIdx(marks) + if winningCardIdx >= 0 { + winningCard := cards[winningCardIdx] + winningMarks := marks[winningCardIdx] + score = getCardScore(winningCard, winningMarks) + + // multiply by the last drawn tile + mult, err := strconv.Atoi(draw) + check(err) + + lastDraw = mult + score *= mult + + break + } + } + + fmt.Printf("After %d draws, card #%d won on a %d with a score of %d\n", drawTimes, winningCardIdx, lastDraw, score) +} + +func part2() { + fmt.Println("* Part 2 *") + fmt.Println(" Goal: Figure out which board will win last.") + fmt.Println(" Answer: Once it wins, what would its final score be?") + + input, err := os.Open("./input.txt") + check(err) + defer input.Close() + + draws, cards := readData(input) + marks := initializeMarks(cards) + + drawTimes := 0 + lastDraw := 0 + score := 0 + foundLoser := false + + for _, draw := range draws { + if foundLoser { + break + } + + drawTimes += 1 + marks = markCards(cards, marks, draw) + winningCardIdx := findWinningCardIdx(marks) + + eliminatedCards := 0 + for winningCardIdx > -1 { + // if there's only one card left, it's the losingist one + if len(marks) == 1 { + score = getCardScore(cards[0], marks[0]) + + // multiply by the last drawn tile + mult, err := strconv.Atoi(draw) + check(err) + + lastDraw = mult + score *= mult + foundLoser = true + break + } + + eliminatedCards += 1 + // if a card wins, just remove it from the list of cards and don't track it anymore + cards[winningCardIdx] = cards[len(cards)-1] + cards[len(cards)-1] = Card{} + cards = cards[:len(cards)-1] + + marks[winningCardIdx] = marks[len(marks)-1] + marks[len(marks)-1] = MarkedCard{} + marks = marks[:len(marks)-1] + + winningCardIdx = findWinningCardIdx(marks) + } + if eliminatedCards > 0 { + //fmt.Printf("Round %d: Eliminated %d cards with a %s (%d remaining)\n", drawTimes, eliminatedCards, draw, len(cards)) + } + } + + //fmt.Println("Card: ", cards[0]) + //fmt.Println("Mark: ", marks[0]) + fmt.Printf("After %d draws, the losingist card won on a %d with a score of %d\n", drawTimes, lastDraw, score) +} diff --git a/day-4/main_test.go b/day-4/main_test.go new file mode 100644 index 0000000..a30f81c --- /dev/null +++ b/day-4/main_test.go @@ -0,0 +1,11 @@ +package main + +import ( + assert "AoC2021/utils/testing" + "testing" +) + +func TestDay4(t *testing.T) { + assert.IsTrue(t, true, "cool") + assert.StrEquals(t, "foo", "bar", "baz") +}