From 80c705488f0a9562b19dff93075cc5115f235a19 Mon Sep 17 00:00:00 2001 From: Kelly Date: Tue, 1 Sep 2026 11:00:11 -0700 Subject: [PATCH] lunarlander-update --- modules/games/README.md | 11 +- modules/games/lunarlander.py | 230 +++++++++++------------------------ 2 files changed, 79 insertions(+), 162 deletions(-) diff --git a/modules/games/README.md b/modules/games/README.md index facd82d..1393396 100644 --- a/modules/games/README.md +++ b/modules/games/README.md @@ -75,11 +75,12 @@ During each turn you'll see: ## Tips for Winning 1. **Early descents:** High initial velocity = more burn needed early -2. **Fuel efficiency:** Lower burn rates = longer mission, but slow descent control -3. **Engine management:** Stay below 70% temp; if warned, reduce burn rate next turn -4. **Final approach:** Use a gentle burn (10-30 lbs/sec) for precision landing -5. **Overheat risk:** Burning 200+ lbs/sec heats engine fast; only use when desperate! -6. **Free fall:** Use 0 lbs/sec strategically to drop quickly then burn hard for landing +2. **Fuel efficiency:** Low burns (≤100 lbs/sec) cool faster than they heat—sustainable for long periods +3. **Engine management:** Higher burns (>300 lbs/sec) produce net heating; use sparingly and cool down between pulses +4. **Thermal sweet spot:** Medium burns (100-200 lbs/sec) offer good balance of control and cooling +5. **Aggressive burns:** 300+ lbs/sec causes rapid heating; only use in emergencies and monitor temp closely +6. **Continuous cooling:** Engine cools even while burning, just slower at high burn rates—no need for full coasting +7. **Final approach:** Use steady, moderate burn (50-100 lbs/sec) for precision landing with engine safety margin ## Game State diff --git a/modules/games/lunarlander.py b/modules/games/lunarlander.py index e604f4e..4985948 100644 --- a/modules/games/lunarlander.py +++ b/modules/games/lunarlander.py @@ -9,7 +9,6 @@ import random from dataclasses import dataclass, asdict from typing import Any, NamedTuple, Tuple, Optional, Dict -PAGE_WIDTH = 64 # Unit conversion helpers def get_distance_display(altitude_miles: float, use_metric: bool = False) -> Tuple[float, str]: @@ -40,6 +39,7 @@ def get_distance_display(altitude_miles: float, use_metric: bool = False) -> Tup feet = int(altitude_miles * 5280) return feet, "ft" + def get_speed_display(velocity_mph: int, use_metric: bool = False) -> Tuple[int, str]: """ Convert velocity to display units. @@ -58,78 +58,12 @@ def get_speed_display(velocity_mph: int, use_metric: bool = False) -> Tuple[int, else: return velocity_mph, "mph" -COLUMN_WIDTH = 2 -SECONDS_WIDTH = 4 -MPH_WIDTH = 6 -ALT_MI_WIDTH = 6 -ALT_FT_WIDTH = 4 -MPH_WIDTH = 6 -FUEL_WIDTH = 8 -BURN_WIDTH = 10 - -SECONDS_LEFT = 0 -SECONDS_RIGHT = SECONDS_LEFT + SECONDS_WIDTH -ALT_LEFT = SECONDS_RIGHT + COLUMN_WIDTH -ALT_MI_RIGHT = ALT_LEFT + ALT_MI_WIDTH -ALT_FT_RIGHT = ALT_MI_RIGHT + COLUMN_WIDTH + ALT_FT_WIDTH -MPH_LEFT = ALT_FT_RIGHT + COLUMN_WIDTH -MPH_RIGHT = MPH_LEFT + MPH_WIDTH -FUEL_LEFT = MPH_RIGHT + COLUMN_WIDTH -FUEL_RIGHT = FUEL_LEFT + FUEL_WIDTH -BURN_LEFT = FUEL_RIGHT + COLUMN_WIDTH -BURN_RIGHT = BURN_LEFT + BURN_WIDTH - class PhysicalState(NamedTuple): velocity: float altitude: float -def add_rjust(line: str, s: Any, pos: int) -> str: - """Add a new field to a line right justified to end at pos""" - s_str = str(s) - slen = len(s_str) - if len(line) + slen > pos: - new_len = pos - slen - line = line[:new_len] - if len(line) + slen < pos: - spaces = " " * (pos - slen - len(line)) - line = line + spaces - return line + s_str - - -def add_ljust(line: str, s: str, pos: int) -> str: - """Add a new field to a line left justified starting at pos""" - s = s - if len(line) > pos: - line = line[:pos] - if len(line) < pos: - spaces = " " * (pos - len(line)) - line = line + spaces - return line + s - - -def format_line_for_report( - t: Any, - miles: Any, - feet: Any, - velocity: Any, - fuel: Any, - burn_rate: str, - is_header: bool, -) -> str: - line = add_rjust("", t, SECONDS_RIGHT) - line = add_rjust(line, miles, ALT_MI_RIGHT) - line = add_rjust(line, feet, ALT_FT_RIGHT) - line = add_rjust(line, velocity, MPH_RIGHT) - line = add_rjust(line, fuel, FUEL_RIGHT) - if is_header: - line = add_rjust(line, burn_rate, BURN_RIGHT) - else: - line = add_ljust(line, burn_rate, BURN_LEFT) - return line - - class SimulationClock: def __init__(self, elapsed_time: float, time_until_next_prompt: float) -> None: self.elapsed_time = elapsed_time @@ -225,32 +159,33 @@ class Capsule: # Build engaging status display alt_str = f"{alt_value} {alt_label}" - msg = f"⏱️ T+{seconds:>6.0f}s \n 🌍 {alt_str:>10} \n 📉 {speed:>5}{speed_label}\n ⛽ Fuel: {fuel:>6}lbs\n" + vi_arrow = "📈" if velocity < 0 else "📉" + msg = f"⏱️T+{seconds:>6.0f}s\n🌍AGL{alt_str:>10}\n{vi_arrow}VI{abs(speed):>5}{speed_label}\n⛽Fuel:{fuel:>6}lbs\n" if self.fuel_per_second > 0: fuel_seconds = self.fuel_time_remaining() - msg += f"🔥 {self.fuel_per_second:>5.0f}lbs/s ({fuel_seconds:>4.0f}s left)\n" + msg += f"🔥{self.fuel_per_second:>5.0f}lbs/s ({fuel_seconds:>4.0f}s left)\n" # Add engine temp indicator if burning if self.engine_temp > 0: - msg += f"🌡️ {self.engine_temp:.0f}%\n" + msg += f"🌡️{self.engine_temp:.0f}%\n" # Add status indicator msg += "\n" # Engine temp warnings take priority if self.engine_temp > 90: - msg += "🔴 CRITICAL: ENGINE OVERHEAT! Reduce burn NOW!" + msg += "🔴CRITICAL: ENGINE OVERHEAT! Reduce burn NOW!" elif self.engine_temp > 70: - msg += "🟠 WARNING: Engine temp rising - watch it!" + msg += "🟠WARNING: Engine temp rising - watch it!" elif speed > (3000 if not use_metric else int(3000 * 0.44704)): - msg += "🚨 CRITICAL: Falling fast! Burn harder NOW!" + msg += "🚨CRITICAL: Falling fast! Burn harder NOW!" elif speed > (1500 if not use_metric else int(1500 * 0.44704)): - msg += "⚠️ WARNING: High descent rate - increase burn" + msg += "⚠️WARNING: High descent rate - increase burn" elif speed > (500 if not use_metric else int(500 * 0.44704)): - msg += "⚡ Descent rate: steady, watch it" + msg += "⚡️Descent rate: steady, watch it" elif speed > (100 if not use_metric else int(100 * 0.44704)): - msg += "✅ Descent rate: controlled" + msg += "✅Descent rate: controlled" else: msg += "🎯 Descent rate: excellent" @@ -284,19 +219,17 @@ class Capsule: ) - - def format_engine_failure(sim_clock: SimulationClock, capsule: Capsule) -> str: """Format engine failure outcome""" mission_time = sim_clock.elapsed_time altitude_at_failure = capsule.altitude velocity_at_failure = int(3600 * capsule.velocity) - result = f"\n💥 ENGINE FAILURE AT T+{mission_time:.1f}s\n" - result += f"📍 Altitude: {int(altitude_at_failure)}mi\n" - result += f"📉 Velocity: {velocity_at_failure} mph (uncontrolled descent!)\n\n" - result += "🔥 Thrusters shut down! Overheating caused catastrophic failure!\n" - result += "☠️ MISSION FAILED - Uncontrolled impact imminent! 💀\n" + result = f"\n💥ENGINE FAILURE AT T+{mission_time:.1f}s\n" + result += f"📍Altitude: {int(altitude_at_failure)}mi\n" + result += f"📉Velocity: {velocity_at_failure} mph (uncontrolled descent!)\n\n" + result += "🔥Thrusters shut down! Overheating caused catastrophic failure!\n" + result += "☠️MISSION FAILED - Uncontrolled impact imminent! 💀\n" return result @@ -311,20 +244,20 @@ def format_landing_result(sim_clock: SimulationClock, capsule: Capsule) -> str: result += f"⛽ Fuel Used: {fuel_used:.0f} lbs\n\n" if w < 0.5: - result += "🎯 PERFECT! Textbook landing! NASA wants to hire you! 🏆" + result += "🎯PERFECT! Textbook landing! NASA wants to hire you!🏆" elif w < 2: - result += "✨ FLAWLESS DESCENT! Gentle as a feather on the lunar surface! 🌟" + result += "✨FLAWLESS DESCENT! Gentle as a feather on the lunar surface!🌟" elif w < 10: - result += "✅ NICE LANDING! Crew is happy, minimal scratches on hull." + result += "✅NICE LANDING! Crew is happy, minimal scratches on hull." elif w < 30: - result += "⚠️ ROUGH LANDING! Crew is shaken but safe. Some hull damage reported." + result += "⚠️ROUGH LANDING! Crew is shaken but safe. Some hull damage reported." elif w < 60: - result += "💔 CRASH LANDING! Cabin pressure dropping... crew wounded but alive! 🏥" + result += "💔CRASH LANDING! Cabin pressure dropping... crew wounded but alive!🏥" elif w < 100: - result += "☠️ KABOOM! You hit harder than a meteor! New crater created! 💀" + result += "☠️KABOOM! You hit harder than a meteor! New crater created!💀" else: crater_size = w * 0.227 - result += f"☠️ CATASTROPHIC FAILURE! You blasted a {crater_size:.0f} FOOT CRATER!\nMission: FAILED 💀" + result += f"☠️CATASTROPHIC FAILURE! You blasted a {crater_size:.0f} FOOT CRATER!\nMission: FAILED💀" return result @@ -411,10 +344,11 @@ def handle_flyaway(sim_clock: SimulationClock, capsule: Capsule) -> bool: return False - def check_engine_failure(burn_rate: float, engine_temp: float, burn_duration: float) -> Tuple[bool, float]: """ - Check if engine fails during burn. + Check if engine fails during burn with continuous thermal dynamics. + Heating and cooling are continuous - even while burning, the engine sheds some heat. + Higher burn rates produce more heating but also reduce cooling effectiveness. Args: burn_rate: Current burn rate (lbs/sec) @@ -427,35 +361,47 @@ def check_engine_failure(burn_rate: float, engine_temp: float, burn_duration: fl - new_temp: Updated engine temperature """ - # If not burning, apply cooling from previous burn + # If not burning, apply strong cooling during coast phase if burn_rate == 0: - # Cooling rates based on last burn intensity - # We estimate from current temp: if hot, we burned hard recently + # Cooling rates based on current temperature (residual heat from burn) if engine_temp > 50: - cooling = 15 # Was burning hard, cool slowly + cooling = 15 # High temp, cool slowly (system still hot) elif engine_temp > 20: - cooling = 30 # Was burning moderately, cool faster + cooling = 30 # Moderate temp, cool faster else: - cooling = 50 # Was barely burning or not, cool rapidly + cooling = 50 # Low temp, cool rapidly return False, max(0, engine_temp - cooling) - # Calculate temp increase based on burn rate (heating while burning) - # 0-300 = safe, 300-450 = risky, >450 = very risky - # At 300 lbs/sec: minimal heating (0.5% per second max) + # HEATING: Calculate temp increase based on burn rate (0-300 safe, 300+ risky) if burn_rate <= 100: - temp_increase = (burn_rate / 100) * 0.2 # 0-0.2% per second + heating_rate = (burn_rate / 100) * 0.2 # 0-0.2% per second elif burn_rate <= 300: - temp_increase = 0.2 + ((burn_rate - 100) / 200) * 0.3 # 0.2-0.5% per second + heating_rate = 0.2 + ((burn_rate - 100) / 200) * 0.3 # 0.2-0.5% per second elif burn_rate <= 450: - temp_increase = 0.5 + ((burn_rate - 300) / 150) * 4.5 # 0.5-5% per second + heating_rate = 0.5 + ((burn_rate - 300) / 150) * 4.5 # 0.5-5% per second else: - temp_increase = 5 + ((burn_rate - 450) / 100) * 25 # 5%+ per second + heating_rate = 5 + ((burn_rate - 450) / 100) * 25 # 5%+ per second - # Calculate total temp increase over burn duration - new_temp = engine_temp + (temp_increase * burn_duration) + # COOLING: Continuous cooling while burning, inversely proportional to burn rate + # Low burns allow effective cooling; high burns overwhelm the cooling system + if burn_rate <= 100: + cooling_rate = 0.3 # Excellent cooling at low burn rates + elif burn_rate <= 300: + cooling_rate = 0.15 # Moderate cooling at medium burn rates + elif burn_rate <= 450: + cooling_rate = 0.08 # Minimal cooling at high burn rates + else: + cooling_rate = 0.03 # Severe cooling deficit at extreme burn rates - # Random failure chance if overheating during the burn + # NET temperature change = heating - cooling (both per second) + net_rate = heating_rate - cooling_rate + new_temp = engine_temp + (net_rate * burn_duration) + + # Clamp temp to valid range + new_temp = max(0, min(new_temp, 100)) + + # Random failure chance if overheating if new_temp > 100: # Guaranteed failure above 100% return True, 100.0 @@ -464,9 +410,9 @@ def check_engine_failure(burn_rate: float, engine_temp: float, burn_duration: fl if random.random() < 0.5: return True, new_temp - # Return heated temp (cooling happens on next free-fall turn) return False, new_temp + def trigger_random_event(capsule: Capsule, sim_clock: SimulationClock) -> Tuple[str, float, float]: """ Randomly trigger space hazards or helpful aliens. @@ -481,31 +427,31 @@ def trigger_random_event(capsule: Capsule, sim_clock: SimulationClock) -> Tuple[ event_roll = random.random() if event_roll < 0.15: # O-ring failure - msg = "🔴 O-RING FAILURE! Thrust reduced to 50% this burn!" + msg = "🔴O-RING FAILURE! Thrust reduced to 50% this burn!" return msg, 0, 0 elif event_roll < 0.35: # Meteor shower altitude_loss = random.uniform(5, 15) - msg = f"☄️ METEOR SHOWER! Lost {altitude_loss:.0f} miles altitude!" + msg = f"☄️METEOR SHOWER! Lost {altitude_loss:.0f} miles altitude!" return msg, 0, -altitude_loss elif event_roll < 0.50: # Solar flare - msg = "⚡ SOLAR FLARE! Instruments blinded for this turn..." + msg = "⚡️SOLAR FLARE! Instruments blinded for this turn..." return msg, 0, 0 elif event_roll < 0.70: # Hostile aliens drain fuel fuel_drain = random.uniform(500, 2000) - msg = f"👽 ALIENS! They drained {fuel_drain:.0f} lbs of fuel! 😱" + msg = f"👽ALIENS! They drained {fuel_drain:.0f} lbs of fuel! 😱" return msg, -fuel_drain, 0 elif event_roll < 0.85: # Helpful aliens add fuel fuel_gain = random.uniform(500, 1500) - msg = f"👽 ALIENS! Wait... they're helping? Gave us {fuel_gain:.0f} lbs! 💚" + msg = f"👽ALIENS! Wait... they're helping? Gave us {fuel_gain:.0f} lbs! 💚" return msg, fuel_gain, 0 else: # Micro-meteorite fuel leak fuel_leak = random.uniform(100, 500) - msg = f"💥 MICRO-METEORITE! Hull breach! {fuel_leak:.0f} lbs fuel leaking!" + msg = f"💥MICRO-METEORITE! Hull breach! {fuel_leak:.0f} lbs fuel leaking!" return msg, -fuel_leak, 0 # No event @@ -640,11 +586,11 @@ class LunarLander: welcome = ( "🚀 LUNAR LANDER 🚀\n" - "Your onboard computer crashed (Boeing made it 😬)\n" + "Your onboard computer crashed(Boeing made it😬)\n" "YOU must land this capsule manually!\n\n" "💡 Enter: burn_rate [duration_sec]\n" "📊 Examples: '100' (10s default) or '150 5'\n" - "⏱️ Watch descent & fuel! Type 'help' for more\n\n" + "⏱️ Watch 🌡️ descent & fuel! Type 'help' for more\n\n" ) status = get_status_message(capsule, sim_clock, use_metric=False) @@ -692,13 +638,12 @@ class LunarLander: if burn_input in ("help", "?", "h"): help_msg = ( "📖 CONTROLS:\n" - "• Burn rate: 0-300 lbs/sec\n" - "• Duration: 1-240 seconds (default 10)\n" - "• Format: '100' or '100 5'\n\n" + "Burn rate: 0-300 lbs/sec\n" + "Duration: 1-240 seconds (default 10)\n" "Examples:\n" - " 100 → Burn at 100 for 10s (default)" - " 150 5 → Burn at 150 for 5s" - " 0 → FREE FALL (no burn)\n\n" + "100 → Burn at 100 for 10s (default)\n" + "150 5 → Burn at 150 for 5s\n" + "0 → FREE FALL (no burn, lower heat)\n\n" "💡 Quick burn = less fuel, longer = more control!" ) return (game_state, help_msg, False) @@ -717,7 +662,7 @@ class LunarLander: if burn_rate < 0: capsule = Capsule.from_dict(game_state['capsule']) sim_clock = SimulationClock.from_dict(game_state['clock']) - error_msg = f"❌ Burn rate cannot be negative!\nUse 0-300 safe, higher = risky!\n\n" + error_msg = f"❌Burn rate cannot be negative!\nUse 0-300 safe, higher = risky!\n\n" error_msg += get_status_message(capsule, sim_clock, use_metric) error_msg += "\n\n→ Try again (e.g., '100' or '100 5'):" return (game_state, error_msg, False) @@ -726,8 +671,8 @@ class LunarLander: # Warn about overheat risk capsule = Capsule.from_dict(game_state['capsule']) sim_clock = SimulationClock.from_dict(game_state['clock']) - warn_msg = f"⚠️ CAUTION: Burn rate {burn_rate} exceeds safe limit (300)!\n" - warn_msg += f"🔥 Engine temp will rise rapidly - risk of failure!\n\n" + warn_msg = f"⚠️CAUTION: Burn rate {burn_rate} exceeds safe limit (300)!\n" + warn_msg += f"🔥Engine temp will rise rapidly - risk of failure!\n\n" warn_msg += get_status_message(capsule, sim_clock, use_metric) warn_msg += f"\n\n→ Proceed with {burn_rate} lbs/sec? (y/n):" # Store pending action @@ -738,7 +683,7 @@ class LunarLander: if burn_duration < 1 or burn_duration > 240: capsule = Capsule.from_dict(game_state['capsule']) sim_clock = SimulationClock.from_dict(game_state['clock']) - error_msg = f"❌ Duration '{burn_duration}' out of range!\nUse 1-240 seconds (default 10)\n\n" + error_msg = f"❌Duration '{burn_duration}' out of range!\nUse 1-240 seconds (default 10)\n\n" error_msg += get_status_message(capsule, sim_clock, use_metric) error_msg += "\n\n→ Try again (e.g., '100' or '100 5'):" return (game_state, error_msg, False) @@ -784,33 +729,4 @@ class LunarLander: status = get_status_message(capsule, sim_clock, use_metric) response = event_display + status + "\n\n→ Next burn rate:" - return game_state, response, game_ended - - -def run_simulation() -> None: - """Legacy function for standalone play - kept for backwards compatibility""" - print() - print( - format_line_for_report("SEC", "MI", "FT", "MPH", "LB FUEL", "BURN RATE", True) - ) - print("(This is a legacy mode - use mesh_bot.py for mesh integration)") - - -def main() -> None: - """Legacy main for standalone play""" - print("LUNAR") - print("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n") - print("THIS IS A COMPUTER SIMULATION OF AN APOLLO LUNAR") - print("LANDING CAPSULE.\n\n") - print("THE ON-BOARD COMPUTER HAS FAILED (IT WAS MADE BY") - print("XEROX) SO YOU HAVE TO LAND THE CAPSULE MANUALLY.\n") - print("SET BURN RATE OF RETRO ROCKETS TO ANY VALUE BETWEEN") - print("0 (FREE FALL) AND 300 (MAXIMUM BURN) POUNDS PER SECOND.") - print("SET NEW BURN RATE EVERY 10 SECONDS.\n") - print("CAPSULE WEIGHT 32,500 LBS; FUEL WEIGHT 16,000 LBS.\n\n\n") - print("GOOD LUCK\n") - print("(Converted to mesh bot - see mesh_bot.py for integration)") - - -if __name__ == "__main__": - main() \ No newline at end of file + return game_state, response, game_ended \ No newline at end of file