🍯 Glaze

package main

import "core:bytes"
import "core:fmt"
import "core:io"
import "core:os"
import "core:slice"

Rucksack :: struct {
	items: []u8,
}

parse_input_file :: proc(filepath: string) -> [dynamic]Rucksack {
	data, ok := os.read_entire_file_from_filename(filepath)
	if !ok {
		panic("oh no, could not read file")
	}

	buf: bytes.Buffer
	bytes.buffer_init(&buf, data)
	defer bytes.buffer_destroy(&buf)

	rucksacks := make([dynamic]Rucksack, 0, 100)

	line: []u8
	err: io.Error
	for ; err != .EOF; line, err = bytes.buffer_read_bytes(&buf, '\n') {
		if len(line) == 0 {
			continue
		}

		append(&rucksacks, Rucksack{items = slice.clone(line[:len(line) - 1])})
	}

	return rucksacks
}

char_to_score :: proc(char: u8) -> int {
	score: int
	if char <= 'Z' {
		score = int(char - 'A') + 27
	} else {
		score = int(char - 'a') + 1
	}
	return score
}

task1 :: proc(rucksacks: []Rucksack) -> int {
	score: int

	for rucksack in rucksacks {
		first := make(map[u8]int)
		defer delete(first)
		last := make(map[u8]int)
		defer delete(last)

		half := len(rucksack.items) / 2
		for i := 0; i < half; i += 1 {
			first[rucksack.items[i]] = 1
			last[rucksack.items[half + i]] = 1
		}

		for item in first {
			if item in last {
				score += char_to_score(item)
			}
		}
	}

	return score
}

task2 :: proc(rucksacks: []Rucksack) -> int {
	score: int

	for i := 0; i < len(rucksacks); i += 3 {
		items := make(map[u8]int)
		defer delete(items)
		for rucksack in rucksacks[i:i + 3] {
			unique_items := make(map[u8]int)
			defer delete(unique_items)

			for item in rucksack.items {
				unique_items[item] += 1
			}

			for item in unique_items {
				items[item] += 1
			}
		}

		for item, amount in items {
			if amount >= 3 {
				score += char_to_score(item)
			}
		}
	}

	return score
}

main :: proc() {
	rucksacks := parse_input_file("input.txt")
	defer delete(rucksacks)

	result1 := task1(rucksacks[:])
	fmt.printf("Task 1 result: %v\n", result1)

	result2 := task2(rucksacks[:])
	fmt.printf("Task 2 result: %v\n", result2)
}