[pyscript/main] searching for <py-config> logger.ts:50:19
[pyscript/main] config loaded:
{
  "schema_version": 1,
  "type": "app",
  "interpreters": [
    {
      "src": "https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js",
      "name": "pyodide-0.23.2",
      "lang": "python"
    }
  ],
  "runtimes": [],
  "packages": [],
  "fetch": [],
  "plugins": [],
  "execution_thread": "main",
  "pyscript": {
    "version": "2022.12.1.dev",
    "time": "2024-11-18T13:30:22.436Z"
  }
} logger.ts:50:19
[py-splashscreen] add py-splashscreen logger.ts:50:19
[py-terminal] No <py-terminal> found, adding one logger.ts:50:19
[pyscript/main] Initializing interpreter logger.ts:50:19
[pyscript/main] Starting the interpreter in the main thread logger.ts:50:19
[pyscript/main] Downloading pyodide-0.23.2... logger.ts:50:19
[pyscript/main] Python startup... logger.ts:50:19
[pyscript/pyodide] importing pyscript logger.ts:50:19
[pyscript/pyodide] Found packages in configuration to install. Loading micropip... logger.ts:50:19
[pyscript/pyodide] pyodide.loadPackage: micropip logger.ts:50:19
[pyscript/pyodide] Loading micropip, packaging logger.ts:50:19
[pyscript/pyodide] Loaded packaging, micropip logger.ts:50:19
[pyscript/pyodide] pyodide loaded and initialized logger.ts:50:19
[pyscript/main] Python ready! logger.ts:50:19
[pyscript/main] Setting up virtual environment... logger.ts:50:19
[pyscript/main] Packages to install:  
Array []
logger.ts:50:19
[pyscript/main] Fetching urls: <empty string> logger.ts:50:19
[pyscript/main] Fetched all paths logger.ts:50:19
[pyscript/main] Plugins to fetch:  
Array []
logger.ts:50:19
[py-script] py-mount: found 0 elements logger.ts:50:19
[py-terminal] Registering stdio listener logger.ts:50:19
[pyscript/main] Executing <py-script> tags... logger.ts:50:19
[pyscript/main] Initializing web components... logger.ts:50:19
[py-script] Initializing py-* event handlers... logger.ts:50:19
[pyscript/main] Startup complete logger.ts:50:19
[py-splashscreen] Closing logger.ts:50:19
[pyscript/main] PyScript page fully initialized logger.ts:50:19
Uncaught (in promise) PythonError: Traceback (most recent call last):
  File "/home/pyodide/pyscript/_internal.py", line 64, in uses_top_level_await
    return TopLevelAwaitFinder().is_source_top_level_await(source)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/pyodide/pyscript/_internal.py", line 46, in is_source_top_level_await
    node = ast.parse(source)
           ^^^^^^^^^^^^^^^^^
  File "/lib/python311.zip/ast.py", line 50, in parse
    return compile(source, filename, mode, flags,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<unknown>", line 663
    document.dispatchEvent(new CustomEvent('input-received', { detail: user_input.value.trim() }))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
    PythonError pyodide.asm.js:9
    new_error pyodide.asm.js:9
    _pythonexc2js pyodide.asm.js:9
    callPyObjectKwargs pyodide.asm.js:9
    callPyObject pyodide.asm.js:9
    apply pyodide.asm.js:9
    innerMessageHandler async_task.ts:84
    callback async_task.ts:154
    pyscript pyscript.js:3556
    __async pyscript.js:3540
    callback async_task.ts:130
    postMessage fake_message_channel.ts:30
    requestResponseMessage request_response.ts:31
    pyscript task.ts:189
    pyscript pyscript.js:3556
    __async pyscript.js:3540
    do_async task.ts:188
    schedule_async task.ts:81
    pyscript task.ts:100
    pyscript pyscript.js:3556
    __async pyscript.js:3540
    then task.ts:99





<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>MARKET: The Trading Game</title>
  
  <!-- Import Google Fonts -->
  <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
  
  <!-- Include PyScript CSS and JS -->
  <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css">
  <script defer src="https://pyscript.net/latest/pyscript.js"></script>
  
  <style>
    /* Reset default margins and paddings */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background-color: #1e1e2f;
      color: #f5f5f5;
      font-family: 'Press Start 2P', sans-serif;
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      overflow: hidden;
    }

    /* Optional: Add a subtle background gradient to avoid external image loading */
    body::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: linear-gradient(135deg, #1e1e2f 25%, #2d2d3a 100%);
      opacity: 0.05;
      pointer-events: none;
    }

    #terminal {
      width: 95vw; /* Increased width from 90vw to 95vw */
      max-width: 1600px; /* Increased max-width from 800px to 1600px */
      height: 95vh; /* Increased height from 90vh to 95vh */
      background-color: #2d2d3a;
      padding: 25px;
      border: 2px solid #44475a;
      box-shadow: 0 0 20px rgba(255, 215, 0, 0.3); /* Gold glow */
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      border-radius: 10px;
      position: relative;
      animation: pulseGlow 5s ease-in-out infinite;
      font-family: 'Roboto Mono', monospace;
    }

    /* Pulsing Glow Animation */
    @keyframes pulseGlow {
      0% {
        box-shadow: 0 0 10px rgba(255, 215, 0, 0.3);
      }
      50% {
        box-shadow: 0 0 30px rgba(255, 215, 0, 0.6);
      }
      100% {
        box-shadow: 0 0 10px rgba(255, 215, 0, 0.3);
      }
    }

    /* Header with Game Title and Logo */
    #header {
      display: flex;
      align-items: center;
      margin-bottom: 10px;
    }

    #logo {
      /* Using an emoji for the logo */
      margin-right: 15px;
      font-size: 48px; /* Adjust font size to match original logo size */
      display: flex;
      align-items: center;
      justify-content: center;
    }

    #game-title {
      font-size: 1.5em;
      font-weight: 700;
      color: #ffd700; /* Gold color */
    }

    #output {
      color: #f1fa8c; /* Light yellow */
      background-color: #1e1e2f;
      padding: 15px;
      margin-bottom: 15px;
      overflow-y: auto;
      flex-grow: 1;
      border-radius: 5px;
      box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
      font-size: 0.75em; /* Adjusted font size for better readability */
      font-family: 'Roboto Mono', monospace; /* Ensure output uses monospace font */
    }

    #input-area {
      display: flex;
      flex-direction: column;
      align-items: stretch;
    }

    #user-input {
      width: 100%;
      height: 80px;
      background-color: #3e3e4a;
      color: #f1fa8c;
      border: 1px solid #44475a;
      padding: 10px;
      outline: none;
      resize: none;
      border-radius: 5px;
      font-size: 1em;
      font-family: 'Roboto Mono', monospace;
      margin-bottom: 10px;
    }

    /* Removed Send Button Styles */

    #rulesButton {
      position: absolute;
      top: 15px;
      right: 15px;
      background-color: #44475a;
      color: #f1fa8c;
      border: none;
      padding: 8px 15px;
      font-size: 0.9em;
      cursor: pointer;
      border-radius: 5px;
      transition: background-color 0.3s, transform 0.2s;
      box-shadow: 0 0 5px rgba(255, 215, 0, 0.5);
      animation: pulseGlow 5s ease-in-out infinite;
      font-family: 'Roboto Mono', monospace;
    }

    #rulesButton:hover {
      background-color: #6272a4;
      transform: translateY(-2px);
    }

    #rulesButton:active {
      background-color: #44475a;
      transform: translateY(0);
    }

    #rulesPopup {
      display: none;
      position: fixed;
      top: 0; left: 0;
      width: 100%; height: 100%;
      background-color: rgba(30, 30, 47, 0.95);
      z-index: 999;
      font-family: 'Roboto', sans-serif;
      padding: 50px;
      overflow-y: auto;
    }

    #rulesContent {
      position: relative;
      background-color: #3e3e4a;
      color: #f1fa8c;
      padding: 30px;
      border-radius: 10px;
      max-width: 800px;
      margin: auto;
      box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
    }

    .closeButton {
      position: absolute;
      top: 15px;
      right: 20px;
      background-color: transparent;
      color: #ff5555;
      border: none;
      font-size: 2em;
      cursor: pointer;
      font-family: 'Roboto Mono', monospace;
    }

    .closeButton:hover {
      color: #ff6e6e;
    }

    #rulesContent h1 {
      text-align: center;
      margin-bottom: 20px;
      color: #ffd700;
    }

    #rulesContent p, #rulesContent ul {
      font-size: 1em;
      line-height: 1.6;
    }

    #rulesContent ul {
      list-style-type: disc;
      margin-left: 20px;
    }

    /* Responsive Adjustments */
    @media (max-width: 1600px) {
      #terminal {
        max-width: 1400px; /* Adjust max-width for slightly smaller screens */
      }
    }

    @media (max-width: 1200px) {
      #terminal {
        width: 100vw;
        max-width: none;
        height: 100vh;
      }
    }

    @media (max-width: 600px) {
      #user-input {
        height: 60px;
      }

      #rulesButton {
        padding: 8px 12px;
        font-size: 0.8em;
      }

      #game-title {
        font-size: 1.2em;
      }
    }
  </style>
</head>
<body>
  <div id="terminal">
    <div id="header">
      <!-- Replaced the img tag with a span containing a money bag emoji -->
      <span id="logo">💰</span>
      <div id="game-title">MARKET: The Trading Game</div>
    </div>
    <div id="output"></div>
    <div id="input-area">
      <textarea id="user-input" placeholder="Enter your trading commands..." disabled></textarea>
      <!-- Send button removed -->
    </div>
  </div>
  <button id="rulesButton" onclick="toggleRules()">Rules</button>
  <div id="rulesPopup">
    <div id="rulesContent">
      <button class="closeButton" onclick="toggleRules()">×</button>
      <h1>Game Rules</h1>
      <p>Welcome to <strong>MARKET: The Trading Game</strong>! Your objective is to accumulate wealth by strategically buying and selling goods across various towns. Here's how to play:</p>
      <ul>
        <li><strong>Commands:</strong> Use commands like <code>BUY</code>, <code>SELL</code>, <code>TRAVEL</code>, <code>STATUS</code>, <code>HELP</code>, and <code>EXIT</code> to interact with the game.</li>
        <li><strong>BUY:</strong> Purchase goods from the current town's market. Ensure you have enough money and cargo space.</li>
        <li><strong>SELL:</strong> Sell goods from your inventory to the current town's market. Maximize your profits by selling high-demand items.</li>
        <li><strong>TRAVEL:</strong> Move to a different town to explore new markets and opportunities.</li>
        <li><strong>STATUS:</strong> View your current money, inventory, cargo space, and remaining turns.</li>
        <li><strong>HELP:</strong> View the game rules and instructions.</li>
        <li><strong>EXIT:</strong> Quit the game at any time.</li>
        <li><strong>Events:</strong> Random events like economic booms, market crashes, and AI traders will influence the market. Adapt your strategies accordingly.</li>
      </ul>
      <p>Good luck, trader! May your investments be prosperous.</p>
    </div>
  </div>

  <!-- JavaScript Section -->
  <script>
    // Define userInput and outputDiv globally
    const userInput = document.getElementById('user-input');
    const outputDiv = document.getElementById('output');

    // Define disableInput and enableInput functions
    window.disableInput = function() {
      userInput.disabled = true;
    }

    window.enableInput = function() {
      userInput.disabled = false;
      userInput.focus();
    }

    document.addEventListener('DOMContentLoaded', (event) => {
      const rulesPopup = document.getElementById('rulesPopup');
      rulesPopup.style.display = 'none';

      // Add event listener for Enter key
      userInput.addEventListener('keypress', function(event) {
          if (event.key === 'Enter' && !event.shiftKey) {
              event.preventDefault(); // Prevent newline in textarea
              handleSend();
          }
      });
    });

    function toggleRules() {
      const rulesPopup = document.getElementById('rulesPopup');
      rulesPopup.style.display = rulesPopup.style.display === 'block' ? 'none' : 'block';
    }

    function handleSend() {
      const commandText = userInput.value.trim();
      if (!commandText) {
        return;
      }
      // Disable input to prevent multiple sends
      window.disableInput();

      const newDiv = document.createElement('div');
      newDiv.innerHTML = `<span style="color: #ffd700;">&gt;</span> ${commandText}`;
      outputDiv.appendChild(newDiv);

      outputDiv.scrollTop = outputDiv.scrollHeight;

      // Dispatch Custom Event with Command Text
      document.dispatchEvent(new CustomEvent('input-received', { detail: commandText }));
      userInput.value = '';
    }

    function instaPrint(message) {
      const newDiv = document.createElement('div');
      newDiv.innerHTML = `<span style="color: #f1fa8c;">${message}</span>`;
      outputDiv.appendChild(newDiv);
      outputDiv.scrollTop = outputDiv.scrollHeight;
    }

    // Removed JS's own slowPrint, fastPrint, etc. functions to prevent conflicts
  </script>

  <!-- PyScript Section -->
  <py-script>
from js import document, window
import asyncio
import random
from pyodide.ffi import create_proxy
from js import Event

# HTML Elements
output_div = document.getElementById('output')
user_input = document.getElementById('user-input')

# Event Handling
def on_input_received(event):
    command = event.detail
    if game_instance.input_mode == "DIFFICULTY_SELECTION":
        asyncio.create_task(game_instance.choose_difficulty_input(command))
    elif game_instance.input_mode == "TRAVEL_SELECTION":
        asyncio.create_task(game_instance.handle_travel_selection(command))
    elif game_instance.input_mode == "BUY_SELECTION":
        asyncio.create_task(game_instance.handle_buy_selection(command))
    elif game_instance.input_mode == "SELL_SELECTION":
        asyncio.create_task(game_instance.handle_sell_selection(command))
    elif game_instance.input_mode == "UPGRADE_CONFIRMATION":
        asyncio.create_task(game_instance.handle_upgrade_confirmation(command))
    elif game_instance.input_mode == "GENERAL_COMMAND":
        asyncio.create_task(game_instance.process_command(command))
    else:
        # Default to processing as a general command
        asyncio.create_task(game_instance.process_command(command))

# Add event listeners
input_received_handler = create_proxy(on_input_received)
document.addEventListener('input-received', input_received_handler)

# Asynchronous Print Functions
async def print_message(message, delay, batch_size=7, color=None):
    # Before starting print, ensure input is disabled
    window.disableInput()
    
    lines = message.split('\n')
    for line in lines:
        temp_div = document.createElement('div')
        if color:
            temp_div.style.color = color
        temp_div.style.fontFamily = 'Roboto Mono, monospace'  # Ensure monospace font
        output_div.appendChild(temp_div)
        partial_message = ''
        i = 0
        while i < len(line):
            # Handle HTML entities (e.g., &lt;, &gt;)
            if line[i] == '&':
                semicolon_index = line.find(';', i)
                if semicolon_index != -1:
                    entity = line[i:semicolon_index+1]
                    partial_message += entity
                    i = semicolon_index + 1
                else:
                    partial_message += line[i]
                    i += 1
            else:
                partial_message += line[i]
                i += 1

            if (i % batch_size == 0) or (i == len(line)):
                temp_div.innerHTML += partial_message
                partial_message = ''
                await asyncio.sleep(delay)
        temp_div.innerHTML += '<br>'
        output_div.scrollTop = output_div.scrollHeight

    # After printing all lines, enable input
    window.enableInput()

async def insta_print(message, color=None):
    # Instant print, disable input during
    window.disableInput()
    
    new_div = document.createElement('div')
    if color:
        new_div.style.color = color
    new_div.style.fontFamily = 'Roboto Mono, monospace'  # Ensure monospace font
    new_div.style.whiteSpace = 'pre-wrap'  # Preserve whitespace
    new_div.textContent = message  # Use textContent instead of innerHTML
    output_div.appendChild(new_div)
    output_div.scrollTop = output_div.scrollHeight
    
    # After instant print, enable input
    window.enableInput()

async def slow_print(message, color=None):
    await print_message(message, delay=0.07, batch_size=5, color=color)

async def fast_print(message, color=None):
    await print_message(message, delay=0.0025, batch_size=5, color=color)

async def medium_print(message, color=None):
    await print_message(message, delay=0.005, batch_size=5, color=color)

# Game Constants and Data
GOODS = {
    "Spices": {"base_price": 100, "weight": 5, "base_supply": 500, "base_demand": 300},
    "Gems": {"base_price": 500, "weight": 10, "base_supply": 200, "base_demand": 150},
    "Food": {"base_price": 50, "weight": 3, "base_supply": 800, "base_demand": 600},
    "Silk": {"base_price": 200, "weight": 4, "base_supply": 300, "base_demand": 250},
    "Iron": {"base_price": 150, "weight": 15, "base_supply": 400, "base_demand": 350},
    "Wine": {"base_price": 120, "weight": 6, "base_supply": 350, "base_demand": 300},
    "Tea": {"base_price": 80, "weight": 2, "base_supply": 600, "base_demand": 500},
    "Herbs": {"base_price": 60, "weight": 3, "base_supply": 700, "base_demand": 550},
    "Cloth": {"base_price": 180, "weight": 8, "base_supply": 250, "base_demand": 200},
    "Gold": {"base_price": 1000, "weight": 20, "base_supply": 100, "base_demand": 80},
}

TOWNS = [
    "Amberville", "Brightstone", "Crimsonburg", "Dusktown",
    "Sunset City", "Northport", "Riverside", "Hilltop",
    "Lakeside", "Oakwood", "Stormhaven", "Fairview"
]

GLOBAL_EVENTS = [
    "Economic boom affecting all markets.",
    "Global trade embargo reducing supply worldwide.",
    "Pandemic affecting demand for certain goods globally.",
    "Technological advancements altering trade dynamics.",
    "Global inflation increasing prices across the board.",
    "Global recession decreasing purchasing power.",
]

LOCAL_EVENTS = [
    "Thieves attacked your caravan and stole some goods!",
    "A storm ruined your goods during travel.",
    "A festival in town increased demand for certain goods.",
    "Disease outbreak decreased the demand for food.",
    "New trade routes discovered, affecting supply.",
    "Pirates disrupted trade in the seas.",
    "Economic boom in Crimsonburg raised prices.",
    "Market crash in Brightstone lowered prices.",
    "Government imposed a tax on trade.",
    "Discovery of a rare gem deposit increased gem supply.",
    "Locusts devastated the food supplies in Dusktown.",
    "Artisans in Amberville created new uses for spices, increasing their demand.",
    "Floods in Riverside increased the supply of iron.",
    "Harvest season in Lakeside boosted the demand for food.",
    "Gold rush in Hilltop increased the supply and demand for gold.",
    "Silk shortage in Sunset City raised silk prices.",
    "Herbal harvest in Oakwood decreased the supply of herbs.",
    "Tea festival in Fairview increased the demand for tea.",
    "Ironworks expansion in Northport increased the supply of iron.",
    "Wine tasting event in Stormhaven boosted wine prices.",
    "Cloth market crash in Fairview lowered cloth prices.",
    "Bandit attack in Oakwood stole some of your goods.",
    "Natural disaster in Dusktown affecting multiple goods.",
    "Town marketplace upgrade in Riverside lowered base prices for certain goods.",
]

DIFFICULTIES = {
    "EASY": {
        "event_chance": 0.15,
        "ai_aggressiveness": 0.8,
        "starting_money": 3000,
        "starting_cargo": 300,
        "turns": 40
    },
    "MEDIUM": {
        "event_chance": 0.2,
        "ai_aggressiveness": 1.0,
        "starting_money": 2000,
        "starting_cargo": 200,
        "turns": 30
    },
    "HARD": {
        "event_chance": 0.25,
        "ai_aggressiveness": 1.2,
        "starting_money": 1500,
        "starting_cargo": 150,
        "turns": 25
    }
}

RELATIONSHIP_BASE = 0
RELATIONSHIP_MAX = 100
RELATIONSHIP_MIN = -100

class AITrader:
    """Represents an AI trader that interacts with the market each turn."""
    def __init__(self, trader_id, aggressiveness=1.0):
        self.trader_id = trader_id
        self.name = f"Trader_{trader_id}"
        self.aggressiveness = aggressiveness  # Determines transaction sizes and frequency

    def perform_action(self, market):
        """AI trader performs buy or sell actions based on strategy."""
        num_actions = random.randint(1, 3) if self.aggressiveness > 1.0 else random.randint(1, 2)
        for _ in range(num_actions):
            action = random.choices(["BUY", "SELL"], weights=[self.aggressiveness, 1.0])[0]
            good = random.choice(list(GOODS.keys()))
            quantity = random.randint(1, int(10 * self.aggressiveness))
            if action == "BUY":
                if market.supply[good] >= quantity:
                    market.supply[good] -= quantity
                    market.demand[good] += quantity * 0.5
            elif action == "SELL":
                market.supply[good] += quantity
                market.demand[good] -= quantity * 0.5
                if market.demand[good] < 50:
                    market.demand[good] = 50

class Market:
    """Represents the market of a specific town."""
    PRICE_FLOOR = 10
    PRICE_CEILING = 2000

    def __init__(self, town):
        self.town = town
        self.supply = {}
        self.demand = {}
        for good, info in GOODS.items():
            supply_variation = random.uniform(0.9, 1.1)
            demand_variation = random.uniform(0.9, 1.1)
            self.supply[good] = info["base_supply"] * supply_variation
            self.demand[good] = info["base_demand"] * demand_variation
        self.prices = {}
        self.generate_prices()
        self.ai_traders = [AITrader(trader_id=i+1, aggressiveness=random.uniform(0.8, 1.2)) for i in range(10)]

    def generate_prices(self):
        """Calculates current prices based on supply and demand with elasticity and price constraints."""
        for good, info in GOODS.items():
            if self.supply[good] == 0:
                price_multiplier = 2
            else:
                ratio = self.demand[good] / self.supply[good]
                elasticity = random.uniform(0.90, 1.10)
                price_multiplier = ratio * elasticity
            new_price = int(GOODS[good]["base_price"] * price_multiplier)
            new_price = max(self.PRICE_FLOOR, min(new_price, self.PRICE_CEILING))
            self.prices[good] = new_price

    async def display_prices(self, relationship):
        """Displays the current prices, supply, demand, and relationship for all goods in the market."""
        status = Game.get_relationship_status(relationship)
        await slow_print(f"\nMarket Overview in {self.town}: Relationship: {relationship} ({status})", color='magenta')
        for good, price in self.prices.items():
            supply = int(self.supply[good])
            demand = int(self.demand[good])
            await fast_print(f"{good}: ${price} (Supply: {supply}, Demand: {demand})")
        await fast_print("")

    def process_ai_traders(self):
        """Processes all AI traders' actions for the market."""
        for trader in self.ai_traders:
            trader.perform_action(self)
        self.generate_prices()

class Player:
    """Represents the player with money, inventory, and cargo capacity."""
    def __init__(self, starting_money, starting_cargo):
        self.money = starting_money
        self.inventory = {good: 0 for good in GOODS}
        self.location = random.choice(TOWNS)
        self.turns = 0
        self.max_cargo = starting_cargo
        self.current_cargo = 0
        self.cargo_upgrades = 0
        self.relationships = {town: RELATIONSHIP_BASE for town in TOWNS}
        self.event_log = []  # To track key events per turn

    async def status(self):
        """Displays the player's current status."""
        await slow_print(f"\nPlayer Status:", color='green')
        await fast_print(f"Location: {self.location}")
        await fast_print(f"Money: ${self.money}")
        await fast_print(f"Cargo: {self.current_cargo}/{self.max_cargo} units used")
        await fast_print("Inventory:")
        for good, qty in self.inventory.items():
            await fast_print(f"  {good}: {qty}")
        await fast_print("")

    def add_cargo(self, good, quantity):
        """Attempts to add cargo to the player's inventory."""
        weight = GOODS[good]["weight"]
        total_weight = weight * quantity
        if self.current_cargo + total_weight > self.max_cargo:
            return False, total_weight
        self.current_cargo += total_weight
        self.inventory[good] += quantity
        return True, total_weight

    def remove_cargo(self, good, quantity):
        """Removes cargo from the player's inventory."""
        weight = GOODS[good]["weight"]
        total_weight = weight * quantity
        self.current_cargo -= total_weight
        if self.current_cargo < 0:
            self.current_cargo = 0
        self.inventory[good] -= quantity
        if self.inventory[good] < 0:
            self.inventory[good] = 0

    async def upgrade_cargo(self):
        """Allows the player to upgrade cargo capacity."""
        upgrade_cost = 500 + (self.cargo_upgrades * 200)
        upgrade_amount = 50
        if self.money < upgrade_cost:
            await slow_print("Insufficient funds to upgrade cargo capacity.")
            return
        confirm = await slow_input(f"Upgrade cargo capacity by {upgrade_amount} for {upgrade_cost} gold? (Y/N): ")
        if confirm.lower() == 'y':
            self.money -= upgrade_cost
            self.max_cargo += upgrade_amount
            self.cargo_upgrades += 1
            await slow_print(f"Cargo capacity increased by {upgrade_amount}. New capacity: {self.max_cargo}")
            self.event_log.append(f"Upgraded cargo capacity by {upgrade_amount} for {upgrade_cost} gold.")
        else:
            await slow_print("Cargo upgrade canceled.")

class Game:
    """Represents the main game loop and logic."""

    def __init__(self):
        self.input_mode = "INIT"  # Modes: INIT, DIFFICULTY_SELECTION, MAIN_GAME, GAME_OVER, TRAVEL_SELECTION, BUY_SELECTION, SELL_SELECTION, UPGRADE_CONFIRMATION
        self.difficulty = None
        self.player = None
        self.turns_total = 0
        self.markets = {}
        self.current_market = None
        self.current_event = None
        self.supply_decay_rate = 0.02
        self.demand_decay_rate = 0.02
        self.turn_counter = 1
        self.game_over = False
        self.key_events = []
        self.selected_buy_good = None
        self.selected_sell_good = None

    @staticmethod
    def get_relationship_status(relation):
        """Converts a numerical relationship score into a descriptive status."""
        if relation > 50:
            return "Friendly"
        elif relation >= -50:
            return "Neutral"
        else:
            return "Hostile"

    async def choose_difficulty_input(self, command):
        """Handles input specifically for difficulty selection."""
        if command.isdigit():
            choice = int(command)
            if 1 <= choice <= len(DIFFICULTIES):
                selected = list(DIFFICULTIES.keys())[choice - 1]
                await fast_print(f"Selected {selected.capitalize()} difficulty.\n")
                self.difficulty = selected
                self.player = Player(
                    starting_money=DIFFICULTIES[self.difficulty]["starting_money"],
                    starting_cargo=DIFFICULTIES[self.difficulty]["starting_cargo"]
                )
                self.turns_total = DIFFICULTIES[self.difficulty]["turns"]
                self.markets = {town: Market(town) for town in TOWNS}
                self.current_market = self.markets[self.player.location]
                self.input_mode = "MAIN_GAME"  # Transition to main game state
                await self.game_turn()
            else:
                await fast_print("Invalid choice. Please enter a number corresponding to the difficulty levels.")
        else:
            await fast_print("Invalid input. Please enter a number.")

    async def handle_travel_selection(self, command):
        """Handles input specifically for travel selection."""
        if command.isdigit():
            choice = int(command)
            available_towns = [town for town in TOWNS if town != self.player.location]
            if 1 <= choice <= len(available_towns):
                new_location = available_towns[choice - 1]
                self.player.location = new_location
                self.current_market = self.markets[new_location]
                await slow_print(f"Traveled to {new_location}.")
                self.input_mode = "MAIN_GAME"
                await self.game_turn()
            else:
                await fast_print("Invalid town number. Please enter a valid number.")
        else:
            await fast_print("Invalid input. Please enter a number.")

    async def handle_buy_selection(self, command):
        """Handles input specifically for buy selection."""
        if command.isdigit():
            choice = int(command)
            goods_list = list(GOODS.keys())
            if 1 <= choice <= len(goods_list):
                selected_good = goods_list[choice - 1]
                await self.buy(selected_good)
            else:
                await fast_print("Invalid good number. Please enter a valid number.")
        else:
            await fast_print("Invalid input. Please enter a number.")

    async def handle_sell_selection(self, command):
        """Handles input specifically for sell selection."""
        if command.isdigit():
            choice = int(command)
            goods_list = [good for good, qty in self.player.inventory.items() if qty > 0]
            if not goods_list:
                await fast_print("You have no goods to sell.")
                self.input_mode = "MAIN_GAME"
                await self.game_turn()
                return
            if 1 <= choice <= len(goods_list):
                selected_good = goods_list[choice - 1]
                await self.sell(selected_good)
            else:
                await fast_print("Invalid good number. Please enter a valid number.")
        else:
            await fast_print("Invalid input. Please enter a number.")

    async def handle_upgrade_confirmation(self, command):
        """Handles input specifically for upgrade confirmation."""
        if command.lower() == 'y':
            await self.player.upgrade_cargo()
        else:
            await slow_print("Cargo upgrade canceled.")
        self.input_mode = "MAIN_GAME"
        await self.game_turn()

    async def choose_difficulty(self):
        """Prompts the player to choose a difficulty level."""
        self.input_mode = "DIFFICULTY_SELECTION"
        await fast_print("Choose Difficulty Level:")
        difficulties = list(DIFFICULTIES.keys())
        for idx, level in enumerate(difficulties, 1):
            await fast_print(f"{idx}. {level.capitalize()}")
        await fast_print(f"Enter the number corresponding to your choice (1-{len(DIFFICULTIES)}): ")

    async def start(self):
        """Starts the game by displaying the title and welcome message."""
        ascii_art_lines = [
            " __  __    _    ____  _  _______ _____ ",
            "|  \\/  |  / \\  |  _ \\| |/ / ____|_   _|",
            "| |\\/| | / _ \\ | |_) | ' /|  _|   | |  ",
            "| |  | |/ ___ \\|  _ <| . \\| |___  | |  ",
            "|_|  |_/_/   \\_\\_| \\_\\_|\\_\\_____| |_|  ",
        ]
        ascii_art = '\n'.join(ascii_art_lines)  # Use newline characters
        await insta_print(ascii_art, color='cyan')
        
        # Insta print lines of '=' signs
        await insta_print("=" * 60, color='yellow')
        await slow_print("Welcome to the Text-Based Trading Game!", color='yellow')
        await insta_print("=" * 60, color='yellow')
        
        # Slow print game introduction
        game_description = (
            "Your goal is to accumulate as much wealth as possible within a set number of turns.\n"
            "Explore a variety of goods, manage your cargo capacity, and navigate the dynamic market."
        )
        await slow_print(game_description, color='yellow')
        await self.choose_difficulty()
        # Input mode is set to DIFFICULTY_SELECTION, awaiting user input via the event handler

    async def game_turn(self):
        """Handles a single turn in the game."""
        if self.turn_counter > self.turns_total:
            await self.end_game()
            return

        await medium_print(f"\n--- Turn {self.turn_counter}/{self.turns_total} ---")
        self.apply_decay()
        for market in self.markets.values():
            market.process_ai_traders()
        self.trigger_event()
        current_relationship = self.player.relationships[self.player.location]
        await self.current_market.display_prices(current_relationship)
        await self.player.status()
        await fast_print("Enter command (BUY/SELL/TRAVEL/STATUS/UPGRADE/OVERVIEW/HELP/EXIT): ")
        self.input_mode = "GENERAL_COMMAND"

    async def process_command(self, command):
        """Processes user command received from input."""
        command = command.lower()
        if self.input_mode != "GENERAL_COMMAND":
            # If not in general command mode, ignore
            return

        if command == "buy":
            await self.buy_command()
        elif command == "sell":
            await self.sell_command()
        elif command == "travel":
            await self.travel_command()
        elif command == "status":
            await self.player.status()
        elif command == "upgrade":
            await self.upgrade_command()
        elif command == "overview":
            await self.market_overview()
        elif command == "help":
            await slow_print("""
Available Commands:
BUY - Purchase goods from the market.
SELL - Sell goods from your inventory.
TRAVEL - Move to a different town.
STATUS - View your current status.
UPGRADE - Upgrade your cargo capacity.
OVERVIEW - View market overview across all towns.
HELP - Display this help message.
EXIT - Exit the game.
""".strip(), color='yellow')
        elif command == "exit":
            await slow_print("Thank you for playing!")
            await self.end_game()
            self.game_over = True
            return
        else:
            await fast_print("Invalid command.")
        
        if not self.game_over:
            self.turn_counter += 1
            await self.game_turn()

    async def buy_command(self):
        """Handles the BUY command."""
        goods_list = list(GOODS.keys())
        await fast_print("Available goods to buy:")
        for idx, good in enumerate(goods_list, 1):
            await fast_print(f"{idx}. {good}")
        await fast_print("Enter the number of the good you want to buy:")
        self.input_mode = "BUY_SELECTION"

    async def buy(self, selected_good):
        """Handles the buying process for the player with a pre-selected good."""
        price = self.current_market.prices[selected_good]
        max_affordable_quantity = self.player.money // price
        available_cargo_space = self.player.max_cargo - self.player.current_cargo
        max_cargo_quantity = available_cargo_space // GOODS[selected_good]["weight"]
        max_supply = int(self.current_market.supply[selected_good])
        max_quantity = min(max_affordable_quantity, max_cargo_quantity, max_supply)

        if max_quantity <= 0:
            await slow_print("You cannot buy any units of this good due to insufficient funds, cargo space, or supply.")
            return

        await fast_print(f"You can afford up to {max_quantity} units.")
        await fast_print(f"Enter the quantity of {selected_good} to buy (max {max_quantity}): ")
        self.input_mode = "BUY_QUANTITY"
        # Store selected_good for quantity processing
        self.selected_buy_good = selected_good

    async def sell_command(self):
        """Handles the SELL command."""
        goods_with_inventory = [good for good, qty in self.player.inventory.items() if qty > 0]
        if not goods_with_inventory:
            await fast_print("You have no goods to sell.")
            return
        await fast_print("Available goods to sell:")
        for idx, good in enumerate(goods_with_inventory, 1):
            await fast_print(f"{idx}. {good}")
        await fast_print("Enter the number of the good you want to sell:")
        self.input_mode = "SELL_SELECTION"

    async def sell(self, selected_good):
        """Handles the selling process for the player with a pre-selected good."""
        if self.player.inventory[selected_good] == 0:
            await slow_print(f"You have no {selected_good} to sell.")
            return

        price = self.current_market.prices[selected_good]
        max_sell_quantity = self.player.inventory[selected_good]
        await fast_print(f"You can sell up to {max_sell_quantity} units of {selected_good}.")
        await fast_print(f"Enter the quantity of {selected_good} to sell (max {max_sell_quantity}): ")
        self.input_mode = "SELL_QUANTITY"
        # Store selected_good for quantity processing
        self.selected_sell_good = selected_good

    async def travel_command(self):
        """Handles the TRAVEL command."""
        available_towns = [town for town in TOWNS if town != self.player.location]
        await fast_print("Available towns to travel to:")
        for idx, town in enumerate(available_towns, 1):
            await fast_print(f"{idx}. {town}")
        await fast_print("Enter the number of the town you want to travel to:")
        self.input_mode = "TRAVEL_SELECTION"

    async def upgrade_command(self):
        """Handles the UPGRADE command."""
        await fast_print("Upgrade Cargo Capacity:")
        await slow_print("Upgrade cargo capacity by 50 units for 500 gold? (Y/N): ")
        self.input_mode = "UPGRADE_CONFIRMATION"

    async def buy_quantity_handler(self, quantity):
        """Handles the quantity input for buying."""
        selected_good = self.selected_buy_good
        price = self.current_market.prices[selected_good]
        cost = price * quantity
        success, added_weight = self.player.add_cargo(selected_good, quantity)
        if not success:
            await slow_print("Not enough cargo space.")
            self.input_mode = "MAIN_GAME"
            await self.game_turn()
            return

        self.player.money -= cost
        self.current_market.supply[selected_good] -= quantity
        self.current_market.demand[selected_good] += quantity * 0.3
        await slow_print(f"Bought {quantity} {selected_good} for ${cost}.")
        self.input_mode = "MAIN_GAME"

    async def sell_quantity_handler(self, quantity):
        """Handles the quantity input for selling."""
        selected_good = self.selected_sell_good
        price = self.current_market.prices[selected_good]
        revenue = price * quantity

        self.player.money += revenue
        self.player.remove_cargo(selected_good, quantity)
        self.current_market.supply[selected_good] += quantity
        self.current_market.demand[selected_good] -= quantity * 0.3
        await slow_print(f"Sold {quantity} {selected_good} for ${revenue}.")
        self.input_mode = "MAIN_GAME"

    async def end_game(self):
        """Displays the final game results and concludes the game."""
        await slow_print("\nGame Over!", color='red')
        await fast_print(f"Final Money: ${self.player.money}", color='yellow')
        total_goods = sum(self.player.inventory.values())
        await fast_print(f"Total Goods: {total_goods}", color='yellow')
        await slow_print("Thank you for playing the Text-Based Trading Game!", color='yellow')

    async def market_overview(self):
        """Displays an overview of all markets."""
        await slow_print("\nMarket Overview Across All Towns:", color='magenta')
        for town, market in self.markets.items():
            relationship = self.player.relationships[town]
            await market.display_prices(relationship)
        await fast_print("")

    def apply_decay(self):
        """Applies decay to supply and demand in all markets."""
        for good, info in GOODS.items():
            for market in self.markets.values():
                market.supply[good] += (info["base_supply"] - market.supply[good]) * self.supply_decay_rate
                market.demand[good] += (info["base_demand"] - market.demand[good]) * self.demand_decay_rate

    def trigger_event(self):
        """Randomly triggers a global or local event."""
        event_chance = DIFFICULTIES[self.difficulty]["event_chance"]
        if random.random() < event_chance:
            event = random.choice(GLOBAL_EVENTS + LOCAL_EVENTS)
            asyncio.create_task(insta_print(f"\nEvent: {event}", color='red'))

# Initialize the Game Instance
game_instance = Game()
asyncio.create_task(game_instance.start())

async def slow_input(prompt):
    """Handles slow input for confirmations and quantities."""
    await slow_print(prompt, color='yellow')
    window.disableInput()
    user_input.value = ''
    user_input.placeholder = 'Awaiting input...'
    user_input.disabled = False

    def on_input(event):
        if event.key == 'Enter' and not event.shiftKey:
            event.preventDefault()
            document.dispatchEvent(new CustomEvent('input-received', { detail: user_input.value.trim() }))
            user_input.value = ''
            user_input.disabled = True
            user_input.placeholder = 'Enter your input...'

    user_input.addEventListener('keypress', on_input, once=True)
    return

  </py-script>
</body> 
</html>