471 lines
13 KiB
Markdown
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*
|