286 lines
4.8 KiB
Go
286 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/jpillora/puzzler/harness/aoc"
|
|
)
|
|
|
|
func main() {
|
|
aoc.Harness(run)
|
|
}
|
|
|
|
type Hand struct {
|
|
Cards string
|
|
Bid int
|
|
}
|
|
|
|
func getCardPower(card rune) int {
|
|
if unicode.IsNumber(card) {
|
|
i, _ := strconv.Atoi(string(card))
|
|
return i
|
|
}
|
|
|
|
if card == 'T' {
|
|
return 10
|
|
}
|
|
|
|
if card == 'J' {
|
|
return 11
|
|
}
|
|
|
|
if card == 'Q' {
|
|
return 12
|
|
}
|
|
|
|
if card == 'K' {
|
|
return 13
|
|
}
|
|
|
|
if card == 'A' {
|
|
return 14
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad card %v", card))
|
|
}
|
|
|
|
func getCardPowerJokerRules(card rune) int {
|
|
if unicode.IsNumber(card) {
|
|
i, _ := strconv.Atoi(string(card))
|
|
return i
|
|
}
|
|
|
|
if card == 'T' {
|
|
return 10
|
|
}
|
|
|
|
if card == 'J' {
|
|
return 0
|
|
}
|
|
|
|
if card == 'Q' {
|
|
return 12
|
|
}
|
|
|
|
if card == 'K' {
|
|
return 13
|
|
}
|
|
|
|
if card == 'A' {
|
|
return 14
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad card %v", card))
|
|
}
|
|
|
|
func getHandTypeRanking(hand Hand) int {
|
|
cardUniq := make(map[string]int)
|
|
|
|
for _, c := range strings.Split(hand.Cards, "") {
|
|
if _, ok := cardUniq[c]; !ok {
|
|
cardUniq[c] = 0
|
|
}
|
|
cardUniq[c] += 1
|
|
}
|
|
|
|
if len(cardUniq) == 1 {
|
|
// Full house, top priority
|
|
return 7
|
|
}
|
|
|
|
if len(cardUniq) == 2 {
|
|
// Either four of a kind or full house
|
|
for _, x := range cardUniq {
|
|
if x == 4 || x == 1 {
|
|
// Four of a kind
|
|
return 6
|
|
} else {
|
|
// Must be full house
|
|
return 5
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(cardUniq) == 3 {
|
|
// Either three of a kind or two pair
|
|
for _, x := range cardUniq {
|
|
if x == 3 {
|
|
// Three of a kind
|
|
return 4
|
|
} else if x == 2 {
|
|
// Two pair
|
|
return 3
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(cardUniq) == 4 {
|
|
// Must be one pair
|
|
return 2
|
|
}
|
|
|
|
// Must be high card
|
|
return 1
|
|
}
|
|
func getHandTypeRankingJokerRules(hand Hand) int {
|
|
cardUniq := make(map[string]int)
|
|
|
|
jokerCount := 0
|
|
for _, c := range strings.Split(hand.Cards, "") {
|
|
if _, ok := cardUniq[c]; !ok {
|
|
cardUniq[c] = 0
|
|
}
|
|
cardUniq[c] += 1
|
|
|
|
if c == "J" {
|
|
jokerCount += 1
|
|
}
|
|
}
|
|
|
|
if len(cardUniq) == 1 {
|
|
// Full house, top priority
|
|
return 7
|
|
}
|
|
|
|
if len(cardUniq) == 2 {
|
|
// Either four of a kind or full house
|
|
for _, x := range cardUniq {
|
|
if x == 4 || x == 1 {
|
|
// Four of a kind
|
|
if jokerCount == 4 || jokerCount == 1 {
|
|
// Can be converted to 5oac
|
|
return 7
|
|
}
|
|
return 6
|
|
} else {
|
|
// Must be full house
|
|
if jokerCount == 3 || jokerCount == 2 {
|
|
// Can be converted to 5oac
|
|
return 7
|
|
}
|
|
return 5
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(cardUniq) == 3 {
|
|
// Either three of a kind or two pair
|
|
for _, x := range cardUniq {
|
|
if x == 3 {
|
|
// Three of a kind
|
|
if jokerCount == 1 || jokerCount == 3 {
|
|
// Can be converted to 4oac
|
|
return 6
|
|
}
|
|
return 4
|
|
} else if x == 2 {
|
|
// Two pair
|
|
if jokerCount == 2 {
|
|
// Can be converted to 4oac
|
|
return 6
|
|
} else if jokerCount == 1 {
|
|
// Can be converted to full house
|
|
return 5
|
|
}
|
|
return 3
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(cardUniq) == 4 {
|
|
// Must be one pair
|
|
if jokerCount == 2 || jokerCount == 1 {
|
|
// Can be converted to 3oac
|
|
return 4
|
|
}
|
|
return 2
|
|
}
|
|
|
|
// Must be high card
|
|
if jokerCount == 1 {
|
|
// Converted to one pair
|
|
return 2
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// on code change, run will be executed 4 times:
|
|
// 1. with: false (part1), and example input
|
|
// 2. with: true (part2), and example input
|
|
// 3. with: false (part1), and user input
|
|
// 4. with: true (part2), and user input
|
|
// the return value of each run is printed to stdout
|
|
func run(part2 bool, input string) any {
|
|
hands := make([]Hand, 0)
|
|
|
|
for _, line := range strings.Split(input, "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
lspl := strings.Split(line, " ")
|
|
bid, _ := strconv.Atoi(lspl[1])
|
|
|
|
hands = append(hands, Hand{lspl[0], bid})
|
|
}
|
|
|
|
if part2 {
|
|
sort.SliceStable(hands, func(i, j int) bool {
|
|
// First compare rank
|
|
hRank1 := getHandTypeRankingJokerRules(hands[i])
|
|
hRank2 := getHandTypeRankingJokerRules(hands[j])
|
|
|
|
if hRank1 != hRank2 {
|
|
return hRank1 < hRank2
|
|
}
|
|
|
|
// Otherwise, compare cards
|
|
for k := 0; k < 5; k++ {
|
|
cRank1 := getCardPowerJokerRules(rune(hands[i].Cards[k]))
|
|
cRank2 := getCardPowerJokerRules(rune(hands[j].Cards[k]))
|
|
|
|
if cRank1 != cRank2 {
|
|
return cRank1 < cRank2
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("Could not sort %v and %v", hands[i].Cards, hands[j].Cards))
|
|
})
|
|
|
|
} else {
|
|
sort.SliceStable(hands, func(i, j int) bool {
|
|
// First compare rank
|
|
hRank1 := getHandTypeRanking(hands[i])
|
|
hRank2 := getHandTypeRanking(hands[j])
|
|
|
|
if hRank1 != hRank2 {
|
|
return hRank1 < hRank2
|
|
}
|
|
|
|
// Otherwise, compare cards
|
|
for k := 0; k < 5; k++ {
|
|
cRank1 := getCardPower(rune(hands[i].Cards[k]))
|
|
cRank2 := getCardPower(rune(hands[j].Cards[k]))
|
|
|
|
if cRank1 != cRank2 {
|
|
return cRank1 < cRank2
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("Could not sort %v and %v", hands[i].Cards, hands[j].Cards))
|
|
})
|
|
}
|
|
// solve part 1 here
|
|
|
|
winnings := 0
|
|
for i, hand := range hands {
|
|
winnings += (i + 1) * hand.Bid
|
|
}
|
|
|
|
return winnings
|
|
}
|