Last active
March 31, 2026 03:48
-
-
Save za3k/d695594048ab8eb6239cb1dafdb97413 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Written by Claude (Anthropic AI) | |
| # Rewritten by Za3k because it was bad style. | |
| from decimal import Decimal, getcontext | |
| from math import comb | |
| from itertools import product | |
| from collections import defaultdict | |
| getcontext().prec = 100 | |
| def coupon_collector(probs): | |
| book_probs = defaultdict(int) | |
| for p in probs: | |
| book_probs[p] += 1 | |
| # Calculate using inclusion-exclusion | |
| # Group by prob only (to take advantage of repeated structure and avoid 2^40 subsets) | |
| group_probs = list(book_probs.keys()) | |
| group_sizes = [book_probs[p] for p in group_probs] | |
| E_T = Decimal(0) | |
| ranges = [range(size + 1) for size in group_sizes] | |
| for state in product(*ranges): | |
| if all(n == 0 for n in state): | |
| continue | |
| total_in_state = sum(state) | |
| multiplicity = 1 | |
| for i, count in enumerate(state): | |
| multiplicity *= comb(group_sizes[i], count) | |
| p_sum = Decimal(0) | |
| for i, count in enumerate(state): | |
| prob = group_probs[i] | |
| p_sum += Decimal(count) * prob | |
| sign = (-1) ** (total_in_state + 1) | |
| contribution = Decimal(multiplicity * sign) / p_sum | |
| E_T += contribution | |
| return E_T | |
| ENCHANTS = """Aqua Affinity I|Bane of Arthropods V|Blast Protection IV|Breach IV|Channeling I|Curse of Binding I|Curse of Vanishing I|Depth Strider III|Density V|Efficiency V|Feather Falling IV|Fire Aspect II|Fire Protection IV|Flame I|Fortune III|Frost Walker II|Impaling V|Infinity I|Knockback II|Looting III|Loyalty III|Luck of the Sea III|Lunge III|Lure III|Mending I|Multishot I|Piercing IV|Power V|Projectile Protection IV|Protection IV|Punch II|Quick Charge III|Respiration III|Riptide III|Sharpness V|Silk Touch I|Smite V|Sweeping Edge III|Thorns III|Unbreaking III""".split("|") | |
| enchantments = [] | |
| for line in ENCHANTS: | |
| parts = line.split() | |
| name = ' '.join(parts[:-1]) | |
| level_map = {'I': 1, 'II': 2, 'III': 3, 'IV': 4, 'V': 5} | |
| max_level = level_map[parts[-1]] | |
| # turns out "is_treasure" wouldn't be useful | |
| enchantments.append((name, max_level)) | |
| n_enchants = len(enchantments) | |
| print(f"Total enchantments: {n_enchants}") | |
| ps1 = [] | |
| ps2 = [] | |
| ps3 = [] | |
| ps4 = [] | |
| # Constants for cure discount calculation | |
| CURE_DISCOUNT = 20 # reputation effect: 20 * 5 multiplier * 0.2 price_multiplier = 20 emeralds | |
| MIN_PRICE = 5 | |
| PRICE_FLOOR = 1 | |
| OPTIMAL_THRESHOLD = PRICE_FLOOR + CURE_DISCOUNT # = 21, max base price that cures to 1 emerald | |
| # Calculate probability for each book under various requirements | |
| for name, max_level in enchantments: | |
| book_offer_prob = Decimal(2) / Decimal(3) | |
| # Step 1: Pick this enchantment | |
| p_enchant = Decimal(1) / Decimal(n_enchants) | |
| # Step 2: Pick max level | |
| p_level = Decimal(1) / Decimal(max_level) | |
| # Step 3: Determine range for base price | |
| # Base price ranges from 5 to (5 + max_level * 10 - 1) | |
| random_range = 5 + max_level * 10 | |
| max_price = MIN_PRICE + random_range - 1 | |
| # Probability of minimum price (5 emeralds) | |
| p_price_optimal = Decimal(1) / Decimal(random_range) | |
| # Probability of price that cures down to 1 emerald (price <= 21) | |
| optimal_count = min(OPTIMAL_THRESHOLD, max_price) - MIN_PRICE + 1 | |
| p_price_cured = Decimal(optimal_count) / Decimal(random_range) | |
| # Combined probabilities for each scenario | |
| ps1.append(book_offer_prob * p_enchant) | |
| ps2.append(book_offer_prob * p_enchant * p_level) | |
| ps3.append(book_offer_prob * p_enchant * p_level * p_price_optimal) | |
| ps4.append(book_offer_prob * p_enchant * p_level * p_price_cured) | |
| print() | |
| E_T = coupon_collector(ps1) | |
| print(f"Expected villagers at any level: {float(E_T):.1f}") | |
| E_T = coupon_collector(ps2) | |
| print(f"Expected villagers at max enchant level: {float(E_T):.1f}") | |
| E_T = coupon_collector(ps3) | |
| print(f"Expected villagers at optimal prices (5 emeralds): {float(E_T):.1f}") | |
| E_T = coupon_collector(ps4) | |
| print(f"Expected villagers at 1 emerald after cure: {float(E_T):.1f}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment