diff --git a/Hands to be tested.txt b/Hands to be tested.txt new file mode 100644 index 0000000..9abe50e --- /dev/null +++ b/Hands to be tested.txt @@ -0,0 +1,44 @@ +// royal flush +"CT CJ CQ CK CA" + +// straight flush +"D8 D9 DT DJ DQ" +"H7 H8 H9 HT HJ" + +// four of a kind +"HT SQ ST DT CT" +"HT SK ST DT CT" +"H8 SQ S8 D8 C8" +"H7 SK S7 D7 C7" + +// full house +"H2 SQ C2 D2 CQ" +"H2 SJ C2 D2 CJ" + +// flush +"HK HQ H2 H4 H5" +"D5 D4 D2 DQ DK" + +// straight +"H3 S7 H5 D6 H4" +"C9 CT SJ D7 H8" +"H4 S5 HA D3 H2" + + +// three of a kind +"H2 SQ S2 D2 CK" +"H2 S7 S2 D2 C9" +"H2 S8 S2 D2 C9" + +// two pairs +"H5 SQ C5 DT CT" +"H9 SQ C9 DT CT" + +// one pair +"H3 S8 H5 D8 CA" +"S4 DA H3 CA HT" + +// high card +"H3 S8 H5 DK CA" +"H3 S8 H5 DK CT" +"H3 S8 H5 DK C2" \ No newline at end of file diff --git a/Texas HoldEm Hand comparison test cases.xlsx b/Texas HoldEm Hand comparison test cases.xlsx new file mode 100644 index 0000000..dab0251 Binary files /dev/null and b/Texas HoldEm Hand comparison test cases.xlsx differ diff --git a/card.go b/card.go new file mode 100644 index 0000000..be81a77 --- /dev/null +++ b/card.go @@ -0,0 +1,6 @@ +package main + +type Card struct { + Suit string // H, D, C, S + Value int // 2-14 (A=14) +} \ No newline at end of file diff --git a/hand.go b/hand.go new file mode 100644 index 0000000..3016787 --- /dev/null +++ b/hand.go @@ -0,0 +1,185 @@ +package main + +import ( + "fmt" + "sort" + "strings" +) + +type Hand struct { + Cards []Card +} + +// NewHand parses an input string and returns a new Hand. +func NewHand(input string) (*Hand, error) { + parts := strings.Fields(input) + if len(parts) != 5 { + return nil, fmt.Errorf("invalid number of cards: %d", len(parts)) + } + + var cards []Card + valueMap := map[byte]int{ + '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, + 'T': 10, 'J': 11, 'Q': 12, 'K': 13, 'A': 14, + } + + for _, p := range parts { + if len(p) != 2 { + return nil, fmt.Errorf("invalid card format: %s", p) + } + suitChar := string(p[0]) + valChar := p[1] + + val, ok := valueMap[valChar] + if !ok { + return nil, fmt.Errorf("invalid value: %c", valChar) + } + cards = append(cards, Card{Suit: suitChar, Value: val}) + } + return &Hand{Cards: cards}, nil +} + +type HandCategory int + +const ( + HighCard HandCategory = iota + Pair + TwoPair + ThreeOfAKind + Straight + Flush + FullHouse + FourOfAKind + StraightFlush + RoyalFlush +) + +var handNames = []string{ + "High Card", "Pair", "Two Pair", "Three of a Kind", "Straight", + "Flush", "Full House", "Four of a Kind", "Straight Flush", "Royal Flush", +} + +type HandResult struct { + Category HandCategory + TieBreakers []int +} + +// Evaluate evaluates the hand and returns its category and tiebreakers. +func (h *Hand) Evaluate() HandResult { + sort.Slice(h.Cards, func(i, j int) bool { + return h.Cards[i].Value > h.Cards[j].Value + }) + + counts := make(map[int]int) + suits := make(map[string]int) + for _, c := range h.Cards { + counts[c.Value]++ + suits[c.Suit]++ + } + + isFlush := len(suits) == 1 + isStraight, straightHigh := h.checkStraight() + + if isFlush && isStraight { + if straightHigh == 14 { + return HandResult{Category: RoyalFlush, TieBreakers: []int{}} + } + return HandResult{Category: StraightFlush, TieBreakers: []int{straightHigh}} + } + + type group struct { + val int + count int + } + var groups []group + for v, c := range counts { + groups = append(groups, group{v, c}) + } + sort.Slice(groups, func(i, j int) bool { + if groups[i].count != groups[j].count { + return groups[i].count > groups[j].count + } + return groups[i].val > groups[j].val + }) + + var tieBreakers []int + for _, g := range groups { + tieBreakers = append(tieBreakers, g.val) + } + if len(groups) > 1 && groups[0].count == 1 { + // Fallback for Straight/Flush Sortierung: sicherstellen, dass es rein nach Wert geht + tieBreakers = nil + for _, c := range h.Cards { + tieBreakers = append(tieBreakers, c.Value) + } + } + + + if groups[0].count == 4 { + return HandResult{Category: FourOfAKind, TieBreakers: []int{groups[0].val, groups[1].val}} + } + if groups[0].count == 3 && groups[1].count == 2 { + return HandResult{Category: FullHouse, TieBreakers: []int{groups[0].val, groups[1].val}} + } + if isFlush { + return HandResult{Category: Flush, TieBreakers: tieBreakers} + } + if isStraight { + return HandResult{Category: Straight, TieBreakers: []int{straightHigh}} + } + if groups[0].count == 3 { + return HandResult{Category: ThreeOfAKind, TieBreakers: []int{groups[0].val, groups[1].val, groups[2].val}} + } + if groups[0].count == 2 && groups[1].count == 2 { + return HandResult{Category: TwoPair, TieBreakers: []int{groups[0].val, groups[1].val, groups[2].val}} + } + if groups[0].count == 2 { + return HandResult{Category: Pair, TieBreakers: []int{groups[0].val, groups[1].val, groups[2].val, groups[3].val}} + } + + return HandResult{Category: HighCard, TieBreakers: tieBreakers} +} + +func (h *Hand) checkStraight() (bool, int) { + isStd := true + for i := 0; i < 4; i++ { + if h.Cards[i].Value != h.Cards[i+1].Value+1 { + isStd = false + break + } + } + if isStd { + return true, h.Cards[0].Value + } + + if h.Cards[0].Value == 14 && h.Cards[1].Value == 5 && h.Cards[2].Value == 4 && h.Cards[3].Value == 3 && h.Cards[4].Value == 2 { + return true, 5 + } + + return false, 0 +} + +// Compare compares the current hand with another hand. +// Returns 1 if the current hand wins, 2 if the other hand wins, and 0 for a tie. +func (h *Hand) Compare(other *Hand) int { + h1Result := h.Evaluate() + h2Result := other.Evaluate() + + if h1Result.Category > h2Result.Category { + return 1 + } + if h2Result.Category > h1Result.Category { + return 2 + } + + for i := 0; i < len(h1Result.TieBreakers); i++ { + if h1Result.TieBreakers[i] > h2Result.TieBreakers[i] { + return 1 + } + if h2Result.TieBreakers[i] > h1Result.TieBreakers[i] { + return 2 + } + } + + return 0 +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..a38f0fd --- /dev/null +++ b/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +// --- Testing --- + +func parseFile(path string) { + file, _ := os.Open(path) + if file == nil { + fmt.Println("Could not open file:", path) + return + } + defer file.Close() + + fscanner := bufio.NewScanner(file) + for fscanner.Scan() { + var input = fscanner.Text() + input = strings.TrimSpace(input) + + if len(input) == 0 { + continue + } + + if strings.HasPrefix(input, "//") { + input = strings.Trim(input, "/ ") + fmt.Printf("\n=== %s ===\n", input) + continue + } + + input = strings.Trim(input, "\",") + + hand, err := NewHand(input) + if err != nil { + fmt.Println("Error:", err) + continue + } + + result := hand.Evaluate() + fmt.Printf("Input: [%s]\nResult: %s\n", input, handNames[result.Category]) + } +} + +// Reads all hands and compares them against each other +func compareFileAllVsAll(path string) { + file, err := os.Open(path) + if err != nil { + fmt.Println("Error opening file:", err) + return + } + defer file.Close() + + type SavedHand struct { + Raw string + Hand *Hand + } + var allHands []SavedHand + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if len(line) == 0 || strings.HasPrefix(line, "//") { + continue + } + + cleanLine := strings.Trim(line, "\",") + + hand, err := NewHand(cleanLine) + if err != nil { + continue + } + + allHands = append(allHands, SavedHand{Raw: cleanLine, Hand: hand}) + } + + count := 0 + for i := 0; i < len(allHands); i++ { + for j := i; j < len(allHands); j++ { + h1 := allHands[i] + h2 := allHands[j] + + winner := h1.Hand.Compare(h2.Hand) + + fmt.Printf("Comparison: [%s] vs [%s]\n", h1.Raw, h2.Raw) + + h1Result := h1.Hand.Evaluate() + h2Result := h2.Hand.Evaluate() + + switch winner { + case 1: + fmt.Printf("Winner: Hand 1 (%s beats %s)\n", handNames[h1Result.Category], handNames[h2Result.Category]) + case 2: + fmt.Printf("Winner: Hand 2 (%s beats %s)\n", handNames[h2Result.Category], handNames[h1Result.Category]) + default: + fmt.Printf("Tie: %s\n", handNames[h1Result.Category]) + } + + fmt.Println() + count++ + } + } + fmt.Printf("Total comparisons: %d\n", count) +} + +// --- Main --- + +func main() { + filename := "Hands to be tested.txt" + + fmt.Println("--- Task 1a: Single Evaluation ---") + parseFile(filename) + + fmt.Println("\n--- Task 1b: Comparison (All vs All) ---") + compareFileAllVsAll(filename) +} \ No newline at end of file diff --git a/poker_test.go b/poker_test.go new file mode 100644 index 0000000..d5ab06a --- /dev/null +++ b/poker_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "testing" +) + +func TestRoyalFlush(t *testing.T) { + hand, _ := NewHand("CT CJ CQ CK CA") + result := hand.Evaluate() + if result.Category != RoyalFlush { + t.Errorf("Expected RoyalFlush, got %s", handNames[result.Category]) + } +} + +func TestStraightFlush(t *testing.T) { + hand, _ := NewHand("D8 D9 DT DJ DQ") + result := hand.Evaluate() + if result.Category != StraightFlush { + t.Errorf("Expected StraightFlush, got %s", handNames[result.Category]) + } +} + +func TestFourOfAKind(t *testing.T) { + hand, _ := NewHand("HT SQ ST DT CT") + result := hand.Evaluate() + if result.Category != FourOfAKind { + t.Errorf("Expected FourOfAKind, got %s", handNames[result.Category]) + } +} + +func TestFullHouse(t *testing.T) { + hand, _ := NewHand("H2 SQ C2 D2 CQ") + result := hand.Evaluate() + if result.Category != FullHouse { + t.Errorf("Expected FullHouse, got %s", handNames[result.Category]) + } +} + +func TestFlush(t *testing.T) { + hand, _ := NewHand("HK HQ H2 H4 H5") + result := hand.Evaluate() + if result.Category != Flush { + t.Errorf("Expected Flush, got %s", handNames[result.Category]) + } +} + +func TestStraight(t *testing.T) { + hand, _ := NewHand("H3 S7 H5 D6 H4") + result := hand.Evaluate() + if result.Category != Straight { + t.Errorf("Expected Straight, got %s", handNames[result.Category]) + } +} + +func TestThreeOfAKind(t *testing.T) { + hand, _ := NewHand("H2 SQ S2 D2 CK") + result := hand.Evaluate() + if result.Category != ThreeOfAKind { + t.Errorf("Expected ThreeOfAKind, got %s", handNames[result.Category]) + } +} + +func TestTwoPair(t *testing.T) { + hand, _ := NewHand("H5 SQ C5 DT CT") + result := hand.Evaluate() + if result.Category != TwoPair { + t.Errorf("Expected TwoPair, got %s", handNames[result.Category]) + } +} + +func TestPair(t *testing.T) { + hand, _ := NewHand("H3 S8 H5 D8 CA") + result := hand.Evaluate() + if result.Category != Pair { + t.Errorf("Expected Pair, got %s", handNames[result.Category]) + } +} + +func TestHighCard(t *testing.T) { + hand, _ := NewHand("H3 S8 H5 DK CA") + result := hand.Evaluate() + if result.Category != HighCard { + t.Errorf("Expected HighCard, got %s", handNames[result.Category]) + } +} \ No newline at end of file