class_name UnlockSimulator extends Node # Load the actual game resources var unlock_collection: UnlockDataCollection = load("res://resources/UnlockData.tres") var inventory_resource: InventoryResource = load("res://resources/InventoryData.tres") # Results tracking var all_results: Array[Dictionary] = [] var results_mutex: Mutex = Mutex.new() # Manual thread pool var num_threads: int = 12 # Increase this for more CPU usage var threads: Array[Thread] = [] var task_queue: Array[Dictionary] = [] var queue_mutex: Mutex = Mutex.new() var completed_count: int = 0 var completed_mutex: Mutex = Mutex.new() var active_threads: int = 0 var threads_done: bool = false var start_time: int = 0 var total_combinations: int = 0 var last_progress_time: int = 0 var monitoring_active: bool = false func _ready(): print("=== Unlock Simulator Started ===") var cpu_count = OS.get_processor_count() print("CPU cores detected: %d" % cpu_count) print("Creating %d worker threads (adjust num_threads variable for more/less)" % num_threads) run_comprehensive_test() func _process(_delta): if monitoring_active: # Only update progress once per second var current_time = Time.get_ticks_msec() if current_time - last_progress_time >= 1000: last_progress_time = current_time update_progress() func update_progress(): """Update progress display""" var current_count = 0 completed_mutex.lock() current_count = completed_count completed_mutex.unlock() # Check if all work is complete if current_count >= total_combinations: monitoring_active = false finish_processing() return var percent = float(current_count) / total_combinations * 100.0 var elapsed = (Time.get_ticks_msec() - start_time) / 1000.0 var rate = current_count / elapsed if elapsed > 0 else 0 var eta_seconds = (total_combinations - current_count) / rate if rate > 0 else 0 # Format ETA var eta_str = "" if eta_seconds > 0: var eta_minutes = int(eta_seconds) / 60 var eta_secs = int(eta_seconds) % 60 if eta_minutes > 0: eta_str = "%dm %ds" % [eta_minutes, eta_secs] else: eta_str = "%ds" % eta_secs else: eta_str = "calculating..." print("Progress: %.1f%% (%d/%d) - %.1f combos/sec - ETA: %s" % [ percent, current_count, total_combinations, rate, eta_str ]) func worker_thread(thread_id: int): """Worker thread function that pulls tasks from the queue""" while true: # Get next task from queue var task_data = null queue_mutex.lock() if task_queue.size() > 0: task_data = task_queue.pop_front() queue_mutex.unlock() # If no more tasks, exit if task_data == null: break # Process the task var result = simulate_rank_combination_pure(task_data.combo, task_data.unlock_data, 100000) # Store result results_mutex.lock() all_results.append(result) results_mutex.unlock() # Increment counter completed_mutex.lock() completed_count += 1 completed_mutex.unlock() func simulate_rank_combination_pure(rank_targets: Dictionary, unlock_data_array: Array, max_ticks: int) -> Dictionary: """Pure simulation function that can run in parallel""" var currency: float = 0.0 var stock: float = 0.0 # Create unlock instances from serialized data var unlocks: Array = [] for unlock_data in unlock_data_array: var unlock = UnlockDataResource.new() unlock.unlock_id = unlock_data.unlock_id unlock.unlock_name = unlock_data.unlock_name unlock.base_cost = unlock_data.base_cost unlock.is_scaling = unlock_data.is_scaling unlock.max_rank = unlock_data.max_rank unlock.cost_scaling_multiplier = unlock_data.cost_scaling_multiplier unlock.effect_scaling_multiplier = unlock_data.effect_scaling_multiplier unlock.base_modifiers = unlock_data.base_modifiers.duplicate() unlock.is_unlocked = false unlock.current_rank = 0 unlocks.append(unlock) var ticks = 0 var purchases: Array[Dictionary] = [] var current_ranks = {} # Initialize current ranks for unlock_id in rank_targets.keys(): current_ranks[unlock_id] = 0 # Helper to check if all targets reached var all_targets_reached = func() -> bool: for unlock_id in rank_targets.keys(): if current_ranks[unlock_id] < rank_targets[unlock_id]: return false return true # Calculate modifiers helper var calc_modifiers = func() -> Dictionary: var mods = { "sale_price_modifier": 1.0, "speed_modifier": 1.0, "efficiency_modifier": 1.0, "wood_respawn_modifier": 1.0, "wood_per_click_modifier": 1.0, "purchase_rate_modifier": 1.0, } for unlock in unlocks: if unlock.is_unlocked: var unlock_modifiers = unlock.get_current_modifiers() for key in unlock_modifiers.keys(): if mods.has(key): mods[key] *= unlock_modifiers[key] return mods var modifiers = calc_modifiers.call() while ticks < max_ticks and currency < 100000.0: # Try to buy the cheapest available unlock that hasn't reached its target var cheapest_unlock_id = null var cheapest_cost = INF var cheapest_unlock_obj = null for unlock_id in rank_targets.keys(): if current_ranks[unlock_id] < rank_targets[unlock_id]: # Find the unlock object var unlock = null for u in unlocks: if u.unlock_id == unlock_id: unlock = u break if unlock and unlock.can_rank_up(): var cost = unlock.get_next_cost() if cost < cheapest_cost and currency >= cost: cheapest_cost = cost cheapest_unlock_id = unlock_id cheapest_unlock_obj = unlock # Purchase the cheapest unlock if found if cheapest_unlock_obj != null: currency -= cheapest_cost cheapest_unlock_obj.unlock() current_ranks[cheapest_unlock_id] += 1 # Recalculate modifiers modifiers = calc_modifiers.call() purchases.append({ "tick": ticks, "unlock_id": cheapest_unlock_id, "unlock_name": cheapest_unlock_obj.unlock_name, "rank": cheapest_unlock_obj.current_rank, "currency": currency, "cost_paid": cheapest_cost, "modifiers_after": modifiers.duplicate() }) # Simulate one tick var items_per_tick = Global.cost_per_whittle * modifiers.get("efficiency_modifier", 1.0) stock += items_per_tick var demand = Global.base_purchase_rate * modifiers.get("purchase_rate_modifier", 1.0) var items_sold = min(stock, demand) stock -= items_sold var price_per_item = Global.base_sale_price * modifiers.get("sale_price_modifier", 1.0) var revenue = items_sold * price_per_item currency += revenue ticks += 1 # Check if we've reached target and 10K if all_targets_reached.call() and currency >= 100000.0: break var success = currency >= 100000.0 return { "rank_targets": rank_targets, "success": success, "ticks": ticks if success else -1, "final_currency": currency, "purchases": purchases, "time_formatted": format_time(ticks) if success else "Failed" } func format_time(ticks: int) -> String: var seconds = ticks var minutes = seconds / 60 var hours = minutes / 60 if hours > 0: return "%dh %dm %ds" % [hours, minutes % 60, seconds % 60] elif minutes > 0: return "%dm %ds" % [minutes, seconds % 60] else: return "%ds" % seconds func generate_rank_combinations(max_ranks_per_unlock: int = 10) -> Array[Dictionary]: """Generate all combinations of ranks for the first 4 unlocks""" var combinations: Array[Dictionary] = [] # Get first 4 unlock IDs var unlock_ids = [] for i in range(min(4, unlock_collection.unlocks.size())): unlock_ids.append(unlock_collection.unlocks[i].unlock_id) print("Generating combinations for unlocks: ", unlock_ids) # Generate all combinations (0 to max_ranks for each unlock) for rank1 in range(max_ranks_per_unlock + 1): for rank2 in range(max_ranks_per_unlock + 1): for rank3 in range(max_ranks_per_unlock + 1): for rank4 in range(max_ranks_per_unlock + 1): # Skip the all-zeros case if rank1 == 0 and rank2 == 0 and rank3 == 0 and rank4 == 0: continue var combination = {} if rank1 > 0: combination[unlock_ids[0]] = rank1 if rank2 > 0: combination[unlock_ids[1]] = rank2 if rank3 > 0: combination[unlock_ids[2]] = rank3 if rank4 > 0: combination[unlock_ids[3]] = rank4 combinations.append(combination) return combinations func serialize_unlock_data() -> Array: """Convert unlock collection to serializable data for threads""" var unlock_data = [] for unlock in unlock_collection.unlocks: unlock_data.append({ "unlock_id": unlock.unlock_id, "unlock_name": unlock.unlock_name, "base_cost": unlock.base_cost, "is_scaling": unlock.is_scaling, "max_rank": unlock.max_rank, "cost_scaling_multiplier": unlock.cost_scaling_multiplier, "effect_scaling_multiplier": unlock.effect_scaling_multiplier, "base_modifiers": unlock.base_modifiers.duplicate() }) return unlock_data func run_comprehensive_test(max_ranks: int = 10): """Test all combinations of ranks up to max_ranks for each unlock""" print("\n=== Available Unlocks ===") for unlock in unlock_collection.unlocks: print("ID: %d | %s | Base Cost: %d | Scaling: %s" % [ unlock.unlock_id, unlock.unlock_name, unlock.base_cost, "Yes" if unlock.is_scaling else "No" ]) print(" Modifiers: ", unlock.base_modifiers) print("\n=== Global Constants ===") print("Base Sale Price: %s" % Global.base_sale_price) print("Base Purchase Rate: %s" % Global.base_purchase_rate) print("Cost Per Whittle: %s" % Global.cost_per_whittle) # Generate all combinations var combinations = generate_rank_combinations(max_ranks) total_combinations = combinations.size() print("\n=== Testing %d Combinations ===" % total_combinations) # Serialize unlock data for threads var unlock_data = serialize_unlock_data() # Fill task queue task_queue.clear() for combo in combinations: task_queue.append({ "combo": combo, "unlock_data": unlock_data }) # Reset counters completed_count = 0 all_results.clear() threads_done = false start_time = Time.get_ticks_msec() last_progress_time = start_time monitoring_active = true # Create and start threads print("Starting %d worker threads..." % num_threads) for i in range(num_threads): var thread = Thread.new() thread.start(worker_thread.bind(i)) threads.append(thread) print("All threads started, processing...") func finish_processing(): """Called when all processing is complete""" print("\nAll combinations complete! Waiting for threads to finish...") # Wait for all threads to finish for thread in threads: thread.wait_to_finish() threads.clear() threads_done = true print("All threads finished. Processing results...") var total_time = (Time.get_ticks_msec() - start_time) / 1000.0 # Print results print("\n=== RESULTS ===") print("Total time: %.1f seconds" % total_time) print("Total combinations tested: %d" % all_results.size()) var successful = all_results.filter(func(r): return r.success) print("Successful strategies: %d" % successful.size()) if successful.size() > 0: # Sort by ticks (fastest first) successful.sort_custom(func(a, b): return a.ticks < b.ticks) print("\n=== TOP 10 FASTEST STRATEGIES ===") for i in range(min(10, successful.size())): var result = successful[i] print("\n#%d: %s (%d ticks)" % [i + 1, result.time_formatted, result.ticks]) # Format ranks with unlock names var rank_display = [] for unlock_id in result.rank_targets.keys(): var unlock_name = get_unlock_name_by_id(unlock_id) var ranks = result.rank_targets[unlock_id] rank_display.append("%s: %d" % [unlock_name, ranks]) print("Target Ranks: %s" % ", ".join(rank_display)) # Show purchase order print("Purchase Order:") for purchase in result.purchases: var key_mods = "" if purchase.has("modifiers_after"): var mods = purchase.modifiers_after key_mods = " [Sale:%.2fx Eff:%.2fx Demand:%.2fx]" % [ mods.get("sale_price_modifier", 1.0), mods.get("efficiency_modifier", 1.0), mods.get("purchase_rate_modifier", 1.0) ] var cost_info = "" if purchase.has("cost_paid"): cost_info = " (paid %d)" % purchase.cost_paid print(" @%s: %s -> Rank %d%s - %.0f currency%s" % [ format_time(purchase.tick), purchase.unlock_name, purchase.rank, cost_info, purchase.currency, key_mods ]) else: print("\nNo successful strategies found!") func get_unlock_name_by_id(unlock_id: int) -> String: """Helper function to get unlock name by ID""" for unlock in unlock_collection.unlocks: if unlock.unlock_id == unlock_id: return unlock.unlock_name return "Unknown" func _exit_tree(): # Clean up threads monitoring_active = false for thread in threads: if thread.is_alive(): thread.wait_to_finish()