lunarlander-update

This commit is contained in:
Kelly committed 2026-09-01 11:00:11 -07:00
1 parent 3c81077967
commit 80c705488f
2 files changed
+78 -161

No files matched your search

+6 -5
View File
@@ -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
+72 -156
View File
@@ -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)
@@ -785,32 +730,3 @@ class LunarLander:
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()