Cookie warnings 3
[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-18T16:53:06.582Z"
  }
} 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 333
    await fast_print(f"You can only buy up to <span class="max-buyable">{max_buyable}</span> units of {selected_good}.", color='yellow')
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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, CustomEvent
import asyncio
import random
from pyodide.ffi import create_proxy

# Constants and Data Definitions
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

# Classes Definitions

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.", color='yellow')
            return

        await slow_print(f"Upgrade Cargo Capacity by {upgrade_amount} units for {upgrade_cost} gold? (Y/N): ", color='yellow')
        confirm = await slow_input("")
        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}", color='green')
            self.event_log.append(f"Upgraded cargo capacity by {upgrade_amount} for {upgrade_cost} gold.")
        else:
            await slow_print("Cargo upgrade canceled.", color='yellow')

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, BUY_QUANTITY, SELL_SELECTION, SELL_QUANTITY, UPGRADE_CONFIRMATION, GENERAL_COMMAND
        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."""
        difficulties = list(DIFFICULTIES.keys())
        if command.isdigit():
            choice = int(command)
            if 1 <= choice <= len(difficulties):
                selected = difficulties[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 = "GENERAL_COMMAND"  # Transition to main game state
                await self.game_turn()
            else:
                await fast_print("Invalid choice. Please enter a number corresponding to the difficulty levels.", color='red')
        else:
            await fast_print("Invalid input. Please enter a number.", color='red')

    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}.", color='green')
                self.input_mode = "GENERAL_COMMAND"
                await self.game_turn()
            else:
                await fast_print("Invalid town number. Please enter a valid number.", color='red')
        else:
            await fast_print("Invalid input. Please enter a number.", color='red')

    async def buy_quantity_handler(self, command):
        """Handles the quantity input for buying."""
        selected_good = self.selected_buy_good
        price = self.current_market.prices[selected_good]
        
        if not command.isdigit():
            await fast_print("Invalid input. Please enter a number.", color='red')
            await self.prompt_buy_quantity(selected_good)  # Reprompt for quantity
            return

        quantity = int(command)
        if quantity <= 0:
            await fast_print("Quantity must be at least 1.", color='red')
            await self.prompt_buy_quantity(selected_good)  # Reprompt for quantity
            return

        # Recalculate max_buyable in case the market or player's state has changed
        player_money = self.player.money
        available_cargo_space = self.player.max_cargo - self.player.current_cargo
        cargo_weight = GOODS[selected_good]["weight"]
        available_supply = int(self.current_market.supply[selected_good])

        max_by_money = player_money // price
        if cargo_weight > 0:
            max_by_cargo = available_cargo_space // cargo_weight
        else:
            max_by_cargo = available_supply

        max_buyable = min(max_by_money, max_by_cargo, available_supply)

        if quantity > max_buyable:
            if max_buyable == 0:
                await fast_print("You cannot buy any units of this good due to insufficient funds, cargo space, or supply.", color='red')
                self.input_mode = "GENERAL_COMMAND"
                self.turn_counter += 1
                await self.game_turn()
            else:
                await fast_print(f"You can only buy up to <span class='max-buyable'>{max_buyable}</span> units of {selected_good}.", color='yellow')
                await self.prompt_buy_quantity(selected_good)  # Reprompt for quantity
            return

        # Proceed with buying
        cost = price * quantity
        self.player.money -= cost
        added, _ = self.player.add_cargo(selected_good, quantity)
        
        if added:
            self.current_market.supply[selected_good] -= quantity
            self.current_market.demand[selected_good] += quantity * 0.3
            await slow_print(f"Bought {quantity} units of {selected_good} for ${cost}.", color='green')
            self.input_mode = "GENERAL_COMMAND"
            self.turn_counter += 1
            await self.game_turn()
        else:
            await fast_print("Failed to add cargo. Please check your cargo capacity.", color='red')
            await self.prompt_buy_quantity(selected_good)  # Reprompt for quantity

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

        if not command.isdigit():
            await fast_print("Invalid input. Please enter a number.", color='red')
            await self.sell(selected_good)  # Reprompt for quantity
            return

        quantity = int(command)
        if quantity <= 0:
            await fast_print("Quantity must be at least 1.", color='red')
            await self.sell(selected_good)  # Reprompt for quantity
            return

        if quantity > inventory_qty:
            await fast_print(f"You can only sell up to {inventory_qty} units of {selected_good}.", color='yellow')
            await self.sell(selected_good)  # Reprompt for quantity
            return

        # Proceed with selling
        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} units of {selected_good} for ${revenue}.", color='green')
        self.input_mode = "GENERAL_COMMAND"
        self.turn_counter +=1
        await self.game_turn()

    async def handle_upgrade_confirmation(self, command):
        """Handles input specifically for upgrade confirmation."""
        if command.lower() == 'y':
            await self.player.upgrade_cargo()
        elif command.lower() == 'n':
            await slow_print("Cargo upgrade canceled.", color='yellow')
        else:
            await fast_print("Invalid input. Please enter Y or N.", color='red')
            await self.upgrade_command()  # Reprompt for confirmation
            return

        self.input_mode = "GENERAL_COMMAND"
        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 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.", color='yellow')
            self.input_mode = "GENERAL_COMMAND"
            self.turn_counter +=1
            await self.game_turn()
            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 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:", color='yellow')
        await slow_print("Upgrade cargo capacity by 50 units for 500 gold? (Y/N): ", color='yellow')
        self.input_mode = "UPGRADE_CONFIRMATION"

    async def prompt_buy_quantity(self, selected_good):
        """Prompts the player to enter the quantity they wish to buy."""
        await slow_print("Enter the quantity you want to buy:")
        self.input_mode = "BUY_QUANTITY"

    async def prompt_sell_quantity(self, selected_good):
        """Prompts the player to enter the quantity they wish to sell."""
        await slow_print("Enter the quantity you want to sell:")
        self.input_mode = "SELL_QUANTITY"

    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.strip().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()
            self.turn_counter +=1
            await self.game_turn()
            return
        elif command == "upgrade":
            await self.upgrade_command()
        elif command == "overview":
            await self.market_overview()
            self.turn_counter +=1
            await self.game_turn()
            return
        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')
            self.turn_counter +=1
            await self.game_turn()
            return
        elif command == "exit":
            await slow_print("Thank you for playing!", color='yellow')
            await self.end_game()
            self.game_over = True
            return
        else:
            await fast_print("Invalid command.", color='red')
            # Do not advance the turn for invalid commands
            await self.game_turn()
            return

        # Only advance the turn if the command requires it
        if command in ["buy", "sell", "travel", "upgrade"]:
            # The turn is advanced in the respective handlers
            pass  # Do nothing here
        else:
            # Advance the turn for commands that don't require additional input
            self.turn_counter +=1
            await self.game_turn()

    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("")
        # Do not advance the turn here; user can continue their turn after viewing overview

    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')

    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())

# Asynchronous Print Functions

async def print_message(message, delay, batch_size=7, color=None):
    """Prints a message asynchronously with a delay."""
    # 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):
    """Instantly prints a message without delay."""
    # 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):
    """Prints a message slowly."""
    await print_message(message, delay=0.07, batch_size=5, color=color)

async def fast_print(message, color=None):
    """Prints a message quickly."""
    await print_message(message, delay=0.0025, batch_size=5, color=color)

async def medium_print(message, color=None):
    """Prints a message at medium speed."""
    await print_message(message, delay=0.005, batch_size=5, color=color)

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

# Event Handling

def on_input_received(event):
    """Handles custom 'input-received' events based on the current game input mode."""
    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":
        # Determine which good was selected based on input
        goods_list = list(GOODS.keys())
        if command.isdigit():
            choice = int(command)
            if 1 <= choice <= len(goods_list):
                selected_good = goods_list[choice - 1]
                game_instance.selected_buy_good = selected_good
                asyncio.create_task(game_instance.prompt_buy_quantity(selected_good))
            else:
                asyncio.create_task(fast_print("Invalid good number. Please enter a valid number.", color='red'))
        else:
            asyncio.create_task(fast_print("Invalid input. Please enter a number.", color='red'))
    elif game_instance.input_mode == "SELL_SELECTION":
        # Determine which good was selected based on input
        goods_with_inventory = [good for good, qty in game_instance.player.inventory.items() if qty > 0]
        if command.isdigit():
            choice = int(command)
            if 1 <= choice <= len(goods_with_inventory):
                selected_good = goods_with_inventory[choice - 1]
                game_instance.selected_sell_good = selected_good
                asyncio.create_task(game_instance.prompt_sell_quantity(selected_good))
            else:
                asyncio.create_task(fast_print("Invalid good number. Please enter a valid number.", color='red'))
        else:
            asyncio.create_task(fast_print("Invalid input. Please enter a number.", color='red'))
    elif game_instance.input_mode == "BUY_QUANTITY":
        asyncio.create_task(game_instance.buy_quantity_handler(command))
    elif game_instance.input_mode == "SELL_QUANTITY":
        asyncio.create_task(game_instance.sell_quantity_handler(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)

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_keypress(event):
        if event.key == 'Enter' and not event.shiftKey:
            event.preventDefault()
            
            # Create and dispatch the CustomEvent with the input value
            custom_event = CustomEvent.new('input-received', {'detail': user_input.value.strip()})
            document.dispatchEvent(custom_event)
            
            user_input.value = ''
            user_input.disabled = True
            user_input.placeholder = 'Enter your input...'

    user_input.addEventListener('keypress', on_keypress, {'once': True})
    return

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