Kings-Eunuch/kings-eunuch/LEARNING_NOTES.md
2026-02-08 09:13:01 -05:00

471 lines
13 KiB
Markdown

# 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<Card>` = a vector (list) that holds Cards
- `Vec<i32>` = a vector that holds integers
- `Option<Card>` = an optional value that might contain a Card
- `Result<String, Error>` = a result that contains either a String or an Error
The `<>` tells Rust "what type goes inside this container."
#### 2. The `Option<T>` 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<T> {
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<Card>
// 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<T, E>` Type
`Result` is for operations that can succeed or fail.
It's also an enum:
```rust
enum Result<T, E> {
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<Card>`?**
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<Card>) -> 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<Card> {
// ^ 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::<HashSet<_>>()` - Remove duplicates:**
```rust
let ranks = vec![Seven, Eight, Seven, Nine];
let unique: HashSet<Rank> = 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*