Today we learned about CamelCards, a game of poker meant to play on the back of a camel. The most interesting part here was the parsing of the cards and figuring out how to properly rank them. Part 2 turned out to be as easy as tracking Jokers.

package main

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"arjenwiersma.nl/aoc/internal/aoc"
)

type Card struct {
	bid    int
	hand   []int
	jokers int
}

func (c *Card) strongerThen(o *Card) bool {
	for i, v := range c.hand {
		if v > o.hand[i] {
			return true
		} else if v < o.hand[i] {
			return false
		}
	}
	return false
}

func (c *Card) rank() int {
	freq := make([]int, 15)
	for _, v := range c.hand {
		if v == 1 { // skip counting the joker
			continue
		}
		freq[v]++
	}

	sort.Ints(freq)

	freq[len(freq)-1] += c.jokers
	strength := 2 * freq[len(freq)-1]
	// full house and 2 pair
	if freq[len(freq)-2] == 2 {
		strength += 1
	}
	return strength
}

func NewCard(s string, bid int, p2 bool) *Card {
	c := &Card{}
	c.bid = bid
	c.jokers = 0
	for p := 0; p < len(s); p++ {
		if s[p]-'0' >= 2 && s[p]-'0' <= 9 {
			c.hand = append(c.hand, int(s[p]-'0'))
		} else {
			x := 10
			switch s[p] {
			case 'A':
				x = 14
			case 'K':
				x = 13
			case 'Q':
				x = 12
			case 'J':
				if p2 {
					c.jokers += 1
					x = 1
				} else {
					x = 11
				}
			case 'T':
				x = 10
			}
			c.hand = append(c.hand, x)
		}
	}
	return c
}

func (c *Card) String() string {
	return fmt.Sprintf("%v (%d)", c.hand, c.bid)
}

func main() {
	content := aoc.AsLines("2023/Day07/input.txt")

	var cards []*Card
	for _, v := range content {
		p := strings.Split(v, " ")
		b, _ := strconv.Atoi(p[1])
		c := NewCard(p[0], b, false)
		cards = append(cards, c)
	}

	startTime := time.Now()
	lessFunc := func(i, j int) bool {
		if cards[i].rank() == cards[j].rank() {
			return cards[j].strongerThen(cards[i])
		}
		return cards[i].rank() < cards[j].rank()
	}

	sort.Slice(cards, lessFunc)

	res := 0
	for i, c := range cards {
		res += (i + 1) * c.bid
	}

	endTime := time.Now()
	elapsed := endTime.Sub(startTime)
	if 251216224 != res {
		panic("Wrong answer")
	}
	fmt.Printf("Part 1: %d (%v)\n", res, elapsed) // 251216224

	cards = []*Card{}
	for _, v := range content {
		p := strings.Split(v, " ")
		b, _ := strconv.Atoi(p[1])
		c := NewCard(p[0], b, true)
		cards = append(cards, c)
	}
	startTime = time.Now()

	sort.Slice(cards, lessFunc)

	res = 0
	for i, c := range cards {
		res += (i + 1) * c.bid
	}
	endTime = time.Now()
	elapsed = endTime.Sub(startTime)
	if 250825971 != res {
		panic("Wrong part 2")
	}
	fmt.Printf("Part 2: %d (%v)\n", res, elapsed) // 250825971
}