# Rust Learning Progress - King's Eunuch Project ## Session 1 - Basic Card & Hand Implementation ### Concepts Learned #### 1. Generics (The `<>` Angle Brackets) **What are generics?** They're like templates or placeholders for types. Think of it like a box that can hold different things: - `Vec` = a vector (list) that holds Cards - `Vec` = a vector that holds integers - `Option` = an optional value that might contain a Card - `Result` = a result that contains either a String or an Error The `<>` tells Rust "what type goes inside this container." #### 2. The `Option` Type `Option` is Rust's way of saying "this might have a value, or it might not." It's an enum with two variants: ```rust enum Option { Some(T), // Has a value None, // No value (like null in other languages) } ``` **Example from our code:** ```rust Card::str_to_card("As") // Returns Option // Either: Some(Card { rank: Ace, suit: Spades }) // Or: None (if the string is invalid) ``` **Why not just use null?** Rust doesn't have null! This forces you to handle the "no value" case explicitly, preventing null pointer errors. #### 3. The `Result` Type `Result` is for operations that can succeed or fail. It's also an enum: ```rust enum Result { Ok(T), // Success - contains the result Err(E), // Failure - contains an error } ``` **Example from our code:** ```rust add_cards_from_str("As Kh") // Returns Result<(), String> // Either: Ok(()) - all cards parsed successfully // Or: Err("Invalid card: Xx") - parsing failed ``` #### 4. Enums with Values (Discriminants) You can assign numeric values to enum variants: ```rust enum HandRank { HighCard = 1, OnePair = 2, TwoPair = 3, // ... } ``` **Why is this useful?** When you derive `PartialOrd` and `Ord`, Rust automatically uses these numbers for comparison: - `Flush > Straight` evaluates to `true` (because 6 > 5) - `FullHouse > TwoPair` evaluates to `true` (because 7 > 3) This makes comparing poker hands super clean! #### 5. Derive Macros - Auto-generating Functionality The `#[derive(...)]` attribute automatically implements traits for your type: ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum HandRank { ... } ``` **What you get:** - `Debug` - Can print with `println!("{:?}", hand_rank)` - `Clone` - Can create copies with `.clone()` - `Copy` - Automatically copied (no need for `.clone()`) - `PartialEq, Eq` - Can use `==` and `!=` - `PartialOrd, Ord` - Can use `<`, `>`, `<=`, `>=` and sort This saves you from writing hundreds of lines of boilerplate code! #### 6. Ownership, Borrowing, and References - The Heart of Rust **The Big Idea:** In Rust, every value has exactly ONE owner at a time. When the owner goes out of scope, the value is dropped (freed from memory). This prevents: - Memory leaks (forgetting to free memory) - Use-after-free bugs (using memory after it's freed) - Data races (two threads modifying the same data) ##### The Three Rules of Ownership 1. **Each value has exactly one owner** 2. **When the owner goes out of scope, the value is dropped** 3. **You can transfer ownership (move) or lend it out (borrow)** ##### Example 1: Ownership Transfer (Move) ```rust let cards1 = Vec::new(); // cards1 owns the vector let cards2 = cards1; // Ownership moves to cards2 // println!("{:?}", cards1); // ❌ ERROR! cards1 no longer owns the vector println!("{:?}", cards2); // ✅ OK, cards2 owns it now ``` **What happened?** The vector moved from `cards1` to `cards2`. `cards1` is now invalid. ##### Example 2: Borrowing with References (&) Instead of giving away ownership, you can **lend** it temporarily: ```rust let cards = vec![card1, card2]; print_cards(&cards); // Borrow it (lend it) print_cards(&cards); // Can borrow again! We still own it println!("{:?}", cards); // ✅ Still works! We still own cards ``` **The `&` means "borrow this, don't take ownership"** ##### Example 3: Mutable Borrowing (&mut) Sometimes you need to modify borrowed data: ```rust let mut hand = Hand::new(); hand.add_card(card); // &mut self - borrows hand mutably // hand can be modified, then returned ``` **The `&mut` means "borrow this AND let me change it"** ##### Why `&[Card]` in Function Parameters? ```rust pub fn is_flush(cards: &[Card]) -> bool ``` Breaking this down: - `&` = borrow (don't take ownership) - `[Card]` = a slice (a view into a sequence of Cards) - `&[Card]` = "borrow a view of some cards" **Why not just `cards: Vec`?** If you did that, the function would TAKE OWNERSHIP of the vector, and it would be destroyed when the function ends! ```rust // Bad: Takes ownership fn bad_flush(cards: Vec) -> bool { // cards is destroyed here when function ends! } let my_cards = vec![card1, card2]; bad_flush(my_cards); // my_cards is GONE! Can't use it anymore! ``` ```rust // Good: Borrows fn is_flush(cards: &[Card]) -> bool { // Just looking at the cards, not taking them } let my_cards = vec![card1, card2]; is_flush(&my_cards); // Lend them temporarily // my_cards still exists! Can use it again! ``` ##### The Borrowing Rules 1. **Many readers OR one writer** - You can have: - Multiple `&` (immutable borrows) at the same time, OR - ONE `&mut` (mutable borrow) at a time - But NEVER both at the same time 2. **References must always be valid** - You can't borrow something that's been destroyed ##### Common Patterns You've Already Used **Pattern 1: Borrowing in a loop** ```rust for card in &hand.cards { // Borrow each card println!("{:?}", card); } // hand.cards still exists after the loop! ``` **Pattern 2: Methods that borrow self** ```rust pub fn add_card(&mut self, card: Card) { // ^^^^^^^^ Borrow self mutably self.cards.push(card) } ``` **Pattern 3: Functions that borrow parameters** ```rust pub fn str_to_card(s: &str) -> Option { // ^ Borrow the string, don't take it } ``` ##### Mental Model: Think of Ownership Like Books - **Owning a book** - It's yours, you can destroy it, give it away - **Borrowing a book (`&`)** - Someone lends it to you to read. You must return it. - **Borrowing with permission to write (`&mut`)** - Someone lends you their book and says you can write notes in it. You must return it when done. - **Multiple people can read the same book (`&`), but only one person can write in it at a time (`&mut`)** ##### Quick Reference | Syntax | Meaning | Can Modify? | Can Use Original? | |--------|---------|-------------|-------------------| | `x` | Ownership | Yes | No (moved) | | `&x` | Immutable borrow | No | Yes | | `&mut x` | Mutable borrow | Yes | Yes (after borrow ends) | #### 7. Closures - Anonymous Functions **What are closures?** Small inline functions using `|params| body` syntax. ```rust // Named function fn double(x: i32) -> i32 { x * 2 } // Equivalent closure let double = |x| x * 2; ``` **Common uses:** With iterator methods like `.map()`, `.filter()`, `.any()`, `.all()` ```rust // Check if any card is an Ace cards.iter().any(|card| card.rank == Rank::Ace) // Get only Hearts cards.iter().filter(|card| card.suit == Suit::Hearts) // Extract all ranks cards.iter().map(|card| card.rank) ``` **Pattern matching in parameters:** ```rust .any(|count| *count >= 5) // count is &i32, dereference with * .any(|&count| count >= 5) // & unwraps, count is i32 ``` #### 8. HashMap - Key-Value Storage **What is it?** Like a dictionary - maps keys to values. ```rust use std::collections::HashMap; let mut map = HashMap::new(); map.insert(Suit::Spades, 5); map.insert(Suit::Hearts, 2); // HashMap looks like: // { Spades: 5, Hearts: 2 } ``` **The counting pattern:** ```rust *map.entry(key).or_insert(0) += 1; ``` Breaking it down: 1. `map.entry(key)` - Get or create entry for key 2. `.or_insert(0)` - If new, start at 0 3. `*...` - Dereference to get the value 4. `+= 1` - Increment it **Common methods:** - `.get(&key)` - Get value for key (returns `Option`) - `.insert(key, value)` - Add/update key-value pair - `.values()` - Get all values (ignoring keys) - `.keys()` - Get all keys (ignoring values) - `.iter()` - Iterate over (key, value) pairs **Example from our code:** ```rust let mut suit_counts = HashMap::new(); for card in cards { *suit_counts.entry(card.suit).or_insert(0) += 1; } // suit_counts now maps each Suit to how many times it appeared suit_counts.values().any(|&count| count >= 5) // Any suit has 5+? ``` #### 9. Advanced Iterator Methods **`.windows(n)` - Sliding windows:** ```rust let numbers = vec![1, 2, 3, 4, 5]; for window in numbers.windows(3) { println!("{:?}", window); } // Output: // [1, 2, 3] // [2, 3, 4] // [3, 4, 5] ``` Used in straight detection to check consecutive ranks! **`.enumerate()` - Get index with value:** ```rust let cards = vec!["As", "Kh", "Qd"]; for (i, card) in cards.iter().enumerate() { println!("Card {}: {}", i + 1, card); } // Output: // Card 1: As // Card 2: Kh // Card 3: Qd ``` **`.collect::>()` - Remove duplicates:** ```rust let ranks = vec![Seven, Eight, Seven, Nine]; let unique: HashSet = ranks.iter().copied().collect(); // unique = {Seven, Eight, Nine} ``` **`.contains()` - Check if item exists:** ```rust let ranks = vec![Ace, Two, Three]; if ranks.contains(&Rank::Ace) { println!("Has an ace!"); } ``` #### 10. Pattern Matching Destructuring **In function parameters:** ```rust // Take reference, extract value for &rank in window { // rank is Rank (not &Rank) for &card in cards { // card is Card (not &Card) if card.rank == rank { // Clean comparison! hand.add_card(card); } } } ``` **Why `&` in patterns extracts:** - `&x` in expression: "create reference TO x" - `&x` in pattern: "extract value FROM reference" Think of it as "unwrapping" the reference! **When to use:** - Destructuring: `for &item in collection` - cleaner code downstream - Explicit deref: `for item in collection` + `*item` - when you need both reference and value Both are idiomatic Rust! ### Special Achievement: Wheel Straight (A-2-3-4-5) The wheel straight is a special case in poker where Ace acts as a LOW card: - Normal: A-K-Q-J-T (Ace high) - Wheel: A-2-3-4-5 (Ace low, **5 is the high card**) **Challenge:** After sorting, ranks look like `[Ace(1), King(2), ..., Five(10), Four(11), Three(12), Two(13)]` The wheel `[Ace, Five, Four, Three, Two]` has values `[1, 10, 11, 12, 13]` - NOT consecutive! **Solution implemented:** 1. Check for Ace presence before loop 2. Inside window loop, check if window[1..4] contains [Five, Four, Three, Two] 3. Build hand with ranks 2-5 first, then add Ace LAST 4. This ensures 5 is the "high card" in the hand, not Ace **Result:** Correctly detects wheel even in 7-card scenarios like `[A, 2, 3, 4, 5, K, Q]`! ### Code Fixed Today #### Issue 1: `Vec::push()` returns nothing **Problem:** ```rust self.cards = self.cards.push(card) // ❌ Won't compile ``` **Why?** `push()` modifies the vector in-place and returns `()` (nothing). **Solution:** ```rust self.cards.push(card) // ✅ Just call it, don't assign ``` #### Issue 2: Missing return statement **Problem:** ```rust pub fn add_cards_from_str(&mut self, s: &str) -> Result<(), String> { for card_str in s.split_whitespace() {} // Empty loop, no return } ``` **Solution:** ```rust pub fn add_cards_from_str(&mut self, s: &str) -> Result<(), String> { for card_str in s.split_whitespace() { match Card::str_to_card(card_str) { Some(card) => self.add_card(card), None => return Err(format!("Invalid card: {}", card_str)), } } Ok(()) // Return success if all cards parsed } ``` ### Current Project Status #### Completed ✓ - Basic card representation (Suit, Rank, Card enums/structs) - Card parsing from strings (e.g., "As" → Ace of Spades) - Hand struct with vector of cards - `add_card()` method - `add_cards_from_str()` method with error handling - Created `evaluation.rs` module - `HandRank` enum with correct poker hand rankings - **`find_flush()` - Full flush detection with best 5 cards** ✓ - HashMap-based suit counting - Works with 5, 6, or 7 cards - Returns highest 5 cards of flush suit - All 5 test cases passing - **`find_straight()` - Full straight detection** ✓ - Detects 5 consecutive ranks - Works with 7 cards (Texas Hold'em) - Special handling for wheel straight (A-2-3-4-5) - Ace correctly placed last in wheel (5 is high card) - All 7 test cases passing (including 7-card wheel edge case!) #### Next Steps - Implement pair/trips/quads detection (use HashMap for rank counting) - Implement full house detection (trips + pair) - Build main `evaluate_hand()` function - Hand comparison logic ### Architecture Decisions Made - Building in layers: Core → Game State → Probability → Training/Bot - Keeping evaluation logic separate from game state - Designing for extensibility (both training tool AND poker bot) - See `ARCHITECTURE.md` for full plan ### Teaching Mode From now on, Claude will guide you through implementation rather than writing code for you. You'll learn by doing! ### Questions to Explore - How do we compare hands? - How do we detect poker hand types (pair, flush, straight, etc.)? - How do we handle the full 52-card deck? - How do we model unknown cards for probability calculations? --- *Last updated: 2026-01-02*