From 5ff9f684042a2d8767cbbcee6c705653e76996b8 Mon Sep 17 00:00:00 2001 From: Isaak Date: Sun, 19 Nov 2023 00:23:35 +0100 Subject: [PATCH] AI improved --- main.py | 408 +++++++++++++++++++++++--------------------------------- 1 file changed, 165 insertions(+), 243 deletions(-) diff --git a/main.py b/main.py index 5669177..8c9a122 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,6 @@ Note: Requires 'rich' and 'json' libraries for functionality. import json import random - from rich.console import Console from rich.table import Table from rich.prompt import Prompt @@ -38,8 +37,36 @@ crafting_recipes = { } +def main(): + console = Console() + competitors_ids = ["Player", "RationalAI", "RiskTakingAI"] + market = Market() + + # Initialise the player + player = Company("Player", [cid for cid in competitors_ids if cid != "Player"], market) + + # Initialise AI competitors + rational_ai = AICompany("RationalAI", competitors_ids, market, risk_tolerance=0.3) + risk_taking_ai = AICompany("RiskTakingAI", competitors_ids, market, risk_tolerance=0.7) + ai_competitors = [rational_ai, risk_taking_ai] + market.companies = {company.player_id: company for company in ai_competitors + [player]} + + # Game loop + for turn in range(1, 11): + console.print(f"\n--- Turn {turn} ---\n", style="grey50") + market.update_market() + + for company in [player] + ai_competitors: + company.update_crafting() + company.make_decision(market, ai_competitors) + + print_ai_actions(ai_competitors) + + final_scores = market.calculate_final_scores() + display_final_scores(console, final_scores, market) + + def load_json(filename): - """Loads JSON data from a file.""" try: with open(filename) as file: return json.load(file) @@ -49,125 +76,82 @@ def load_json(filename): class Market: - """Manages market dynamics, including companies, products, stock transactions, and economic indicators.""" def __init__(self): self.current_turn = 0 self.companies = {} self.products = {'A': 10.0, 'B': 15.0, 'C': 20.0} + self.starting_prices = self.products.copy() self.events = load_json('market_events.json')["events"] self.adjust_prices = lambda adjustment: {k: max(1, v + adjustment) for k, v in self.products.items()} self.event_effects = {"double": lambda x: x * 2, "halve": lambda x: x / 2, "increase": lambda x: x + 3.0, "decrease": lambda x: max(1, x - 3.0)} self.stock_ledger = {} - - # Economic indicators - self.inflation_rate = 0.02 # Example starting inflation rate (2%) - self.unemployment_rate = 0.05 # Example starting unemployment rate (5%) - self.gdp = 500000 # Example starting GDP value + self.inflation_rate = 0.02 + self.unemployment_rate = 0.05 + self.gdp = 500000 def update_stock_ledger(self, company_id, owner_id, amount): - """Updates the ledger with the given stock transaction details.""" - key = (company_id, owner_id) - self.stock_ledger[key] = self.stock_ledger.get(key, 0) + amount + self.stock_ledger[company_id, owner_id] = self.stock_ledger.get((company_id, owner_id), 0) + amount def get_stock_ownership(self, company_id, owner_id): - """Retrieves stock ownership details for a specified company and owner.""" return self.stock_ledger.get((company_id, owner_id), 0) def get_stock_price(self, company_id): - """Returns the current stock price of the specified company.""" - company = self.companies[company_id] - company_value = company.value # No argument passed - return round(company_value / 100.0, 2) + return round(self.companies[company_id].value / 100.0, 2) def update_market(self): - """Applies market events, adjusts prices, and updates economic indicators for the current turn.""" self.current_turn += 1 trend_adjustment = random.choice([2, -2, 0]) - self.products = self.adjust_prices(trend_adjustment) - event = random.choices(self.events, weights=[e["probability"] for e in self.events])[0] if event["effect"] in ["new_competitor", "exit_competitor"]: self.handle_competitor_event(event["effect"]) else: self.products = {k: self.event_effects[event["effect"]](v) for k, v in self.products.items()} - self.update_economic_indicators() def adjust_prices(self): - """Adjusts market product prices based on inflation and other factors.""" for product, price in self.products.items(): inflation_adjustment = price * self.inflation_rate - new_price = price + inflation_adjustment - - # small random fluctuation for realism fluctuation = random.uniform(-0.03, 0.03) - new_price += fluctuation - - new_price = max(1.5, new_price) - self.products[product] = round(new_price, 2) - + self.products[product] = round(max(1.5, price + inflation_adjustment + fluctuation), 2) return self.products def handle_competitor_event(self, effect): - """Handles market changes due to new or exiting competitors.""" adjustment = float(random.randint(1, 3)) self.products = {k: max(1.0, v - adjustment) if effect == "new_competitor" else v + adjustment for k, v in self.products.items()} def update_economic_indicators(self): - """Updates market's economic indicators like inflation and unemployment rates.""" - # Update the inflation rate based on market conditions - self.inflation_rate += random.uniform(-0.01, 0.01) # Random fluctuation - self.inflation_rate = max(0.5, self.inflation_rate) # Ensure non-negative - - # Update the unemployment rate - self.unemployment_rate += random.uniform(-0.005, 0.005) # Random fluctuation - self.unemployment_rate = min(max(self.unemployment_rate, 0), 1) # Bound between 0 and 1 - - # Update GDP based on market performance + self.inflation_rate = max(0.5, self.inflation_rate + random.uniform(-0.01, 0.01)) + self.unemployment_rate = min(max(self.unemployment_rate + random.uniform(-0.005, 0.005), 0), 1) self.gdp += self.gdp * (random.uniform(-0.01, 0.03) + self.inflation_rate) def calculate_final_scores(self): - """Calculates and returns final scores based on company values and stock ownership.""" final_scores = {} for company_id, company in self.companies.items(): - # Use the 'value' property instead of the 'get_value' method final_score = company.value - - # Determine the majority owner majority_owner = max(company.own_stock_ownership, key=lambda owner: (company.own_stock_ownership[owner], owner)) majority_percentage = company.own_stock_ownership[majority_owner] - - # Check if the majority owner owns 51% or more is_major_owner = majority_percentage >= 51 - - # Initialize or update the score if company_id not in final_scores: final_scores[company_id] = {'score': 0, 'note': '', 'majority_owner': majority_owner} - - # Distribute scores for owner_id, percentage in company.own_stock_ownership.items(): if percentage > 20: - score_transfer = final_score * (percentage / 100) - if owner_id not in final_scores: - final_scores[owner_id] = {'score': 0, 'note': '', 'majority_owner': ''} - final_scores[owner_id]['score'] += score_transfer - - # If no one owns 51% or more, assign the remainder to the company itself + final_scores[owner_id] = final_scores.get(owner_id, {'score': 0, 'note': '', 'majority_owner': ''}) + final_scores[owner_id]['score'] += final_score * (percentage / 100) if not is_major_owner: remaining_score = final_score - sum( final_scores.get(owner, {'score': 0})['score'] for owner in company.own_stock_ownership) final_scores[company_id]['score'] += remaining_score - return final_scores class Company: """Represents a company within the market, managing finances, inventory, and stock transactions.""" - def __init__(self, player_id, competitors_ids, market=None): + + def __init__(self, player_id, competitors_ids, market=None, debug=False): self.player_id = player_id self.cash = 500.0 self.inventory = {'A': 0, 'B': 0, 'C': 0} @@ -176,6 +160,7 @@ class Company: self.stock_holdings = {cid: 0 for cid in set([player_id] + competitors_ids + ["RationalAI", "RiskTakingAI"])} self.total_shares = 100 self._market = market + self._debug = debug @property def value(self): @@ -184,7 +169,8 @@ class Company: def craft_product(self, recipe_key): """Processes crafting of a product based on the chosen recipe.""" - print(f"Inventory before crafting: {self.inventory}") + if self._debug: + print(f"Inventory before crafting: {self.inventory}") recipe = crafting_recipes[recipe_key] if all(self.inventory[product] >= quantity for product, quantity in recipe['input'].items()): self.crafting_queue.append({'recipe': recipe, 'turns_remaining': recipe['turns']}) @@ -192,34 +178,42 @@ class Company: print("Crafting order placed.") else: print("Not enough resources to craft.") - print(f"Inventory after crafting: {self.inventory}") + if self._debug: + print(f"Inventory after crafting: {self.inventory}") def _update_inventory(self, items, decrease=False): """Updates the inventory based on the given items.""" - print(f"Inventory before update: {self.inventory}") + if self._debug: + print(f"Inventory before update: {self.inventory}") for product, quantity in items.items(): if decrease: self.inventory[product] -= quantity else: self.inventory[product] += quantity - print(f"Inventory after update: {self.inventory}") + if self._debug: + print(f"Inventory after update: {self.inventory}") def update_crafting(self): """Updates the crafting queue, completing orders as their turns conclude.""" - print(f"Crafting queue before update: {self.crafting_queue}") + if self._debug: + print(f"Crafting queue before update: {self.crafting_queue}") completed_orders = [] for order in self.crafting_queue: - print(f"Processing order: {order}") + if self._debug: + print(f"Processing order: {order}") order['turns_remaining'] -= 1 if order['turns_remaining'] == 0: - print(f"Completing order: {order}") - self._update_inventory(order['recipe']['output'], decrease=False) - print(f"Inventory after completing order: {self.inventory}") + if self._debug: + print(f"Completing order: {order}") + self._update_inventory(order['recipe']['output']) + if self._debug: + print(f"Inventory after completing order: {self.inventory}") completed_orders.append(order) for order in completed_orders: self.crafting_queue.remove(order) - print(f"Crafting queue after update: {self.crafting_queue}") + if self._debug: + print(f"Crafting queue after update: {self.crafting_queue}") def trade_product(self, market, product, quantity, buying=True): """Executes a product trade transaction based on the specified parameters.""" @@ -265,33 +259,34 @@ class Company: if self.cash < total_value: return "Insufficient funds to buy stocks." - self._update_stock_ownership(company_id, amount, total_value, buying=True) - message = f"Bought {amount} stocks of {company_id}." + # Update stock ownership if is_ai: - self.last_action = message + self.stock_holdings[company_id] += amount # Increase AI's stock holdings else: - print(message) - return message + self.own_stock_ownership[company_id] += amount # Increase player's own stock holdings + + self.cash -= total_value # Deduct the cost from the buyer's cash + return f"Bought {amount} stocks of {company_id}." def _sell_stock(self, company_id, amount, total_value, is_ai): """Handles the selling of stocks for the specified company.""" + # Check if the seller has enough stocks to sell if self._get_stock_ownership(company_id) < amount: return "Not enough stocks to sell." - self._update_stock_ownership(company_id, amount, total_value, buying=False) - message = f"Sold {amount} stocks of {company_id}." + # Update stock ownership if is_ai: - self.last_action = message + self.stock_holdings[company_id] -= amount # Decrease AI's stock holdings else: - print(message) - return message + self.own_stock_ownership[company_id] -= amount # Decrease player's own stock holdings + + self.cash += total_value # Add the proceeds to the seller's cash + return f"Sold {amount} stocks of {company_id}." def _calculate_available_shares(self, company_id): """Calculates the number of available shares for a given company.""" - if company_id == self.player_id: - return self.total_shares - sum(self.own_stock_ownership.values()) + self.own_stock_ownership[self.player_id] - else: - return self.total_shares - self.stock_holdings.get(company_id, 0) + total_owned = sum(self.stock_holdings.values()) + sum(self.own_stock_ownership.values()) + return self.total_shares - total_owned def _get_stock_ownership(self, company_id): """Retrieves the stock ownership amount for a given company.""" @@ -316,12 +311,12 @@ class Company: def make_decision(self, market, competitors): """Presents decision options to the user and processes the chosen action.""" console = Console() - status_table = Table(title=f"[bold green]{self.player_id}'s Turn - Turn {market.current_turn}", box=box.ROUNDED) + status_table = Table(title=f"[bold cyan]{self.player_id}'s Turn - Turn {market.current_turn}", box=box.ROUNDED) status_table.add_column("Category", style="bold cyan") status_table.add_column("Details") - status_table.add_row("Cash", f"[bold yellow]{self.cash:.2f} €") + status_table.add_row("Cash", f"[bold blue]{self.cash:.2f} €") status_table.add_row("Inventory", ', '.join( - [f"[bold magenta]{item}: {quantity}" for item, quantity in self.inventory.items()])) + [f"[bold]{item}: {quantity}" for item, quantity in self.inventory.items()])) status_table.add_row("Market Prices", ', '.join( [f"[bold blue]{product}: {price:.2f} €" for product, price in market.products.items()])) shareholders = ', '.join( @@ -341,7 +336,7 @@ class Company: choices_display = "\n".join([f"{key}: {value}" for key, value in actions.items()]) console.print(f"Available Actions:\n{choices_display}", style="bold") - action_choice = Prompt.ask("Choose your action", choices=list(actions.keys()), default="4") + action_choice = Prompt.ask("Choose your action", default="4") selected_action = actions.get(action_choice, None) if selected_action == "Trade Products": @@ -427,186 +422,113 @@ class Company: return all(price > 20 for price in self._market.products.values()) +def is_market_bust(market): + return all(price < 0.75 * market.starting_prices[product] for product, price in market.products.items()) + + class AICompany(Company): - """Represents an AI-driven company with automated trading, crafting, and stock actions.""" - def __init__(self, player_id, competitors_ids, market): + def __init__(self, player_id, competitors_ids, market, risk_tolerance): super().__init__(player_id, competitors_ids, market) - self.last_action = "None" + self.risk_tolerance = risk_tolerance + self.actions_history = [] self.average_prices = {product: market.products[product] for product in market.products} def update_average_prices(self): - """Updates the average prices of products based on the current market turn.""" - turn = self._market.current_turn - for product, price in self._market.products.items(): - self.average_prices[product] = (self.average_prices[product] * (turn - 1) + price) / turn + self.average_prices = {product: (self.average_prices[product] * (self._market.current_turn - 1) + price) / self._market.current_turn + for product, price in self._market.products.items()} - def choose_best_crafting_option(self): - """Selects the most viable crafting option based on current inventory.""" - return next((recipe_key for recipe_key, recipe in crafting_recipes.items() - if all(self.inventory[prod] >= qty for prod, qty in recipe['input'].items())), None) + def make_decision(self, market, competitors): + if self.risk_tolerance > 0.5: # High-Risk AI + self.high_risk_decision(market, competitors) + else: # Low-Risk AI + self.low_risk_decision(market, competitors) - def perform_trade_action(self, action): - """Performs a trade action, either buying or selling, based on the specified action.""" - if action == 'sell': - self.sell_inventory_items() - elif action == 'buy': - self.buy_inventory_items() + def low_risk_decision(self, market, competitors): + if market.current_turn == 1: + self.buy_product('A', self.cash / 3) + elif market.current_turn == 2: + self.buy_product('B', min(self.inventory['A'], self.cash / market.products['B'])) + elif market.current_turn >= 3: + self.buy_and_craft(market) - def sell_inventory_items(self): - """Executes the selling of inventory items.""" + def high_risk_decision(self, market, competitors): + if market.current_turn == 1: + self.sell_own_shares(market) + self.buy_product('A', self.cash / 2) + self.buy_product('B', self.cash) + else: + if self.should_craft(market): + self.buy_and_craft(market) + if self.should_sell_products(market): + self.sell_high_value_products(market) + + def buy_product(self, product, budget): + quantity = int(budget // self._market.products[product]) + if quantity > 0: + self.trade_product(self._market, product, quantity) + action = f"Bought {quantity} of {product}" + self.actions_history.append(action) + + def sell_own_shares(self, market): + amount_to_sell = int(self.own_stock_ownership[self.player_id] * 0.25) # Sell 25% of own shares + if amount_to_sell > 0: + self.trade_stock('sell', market, self.player_id, amount_to_sell, is_ai=True) + action = f"Sold {amount_to_sell} of own shares" + self.actions_history.append(action) + + def should_craft(self, market): + return all(self.inventory[product] >= qty for product, qty in crafting_recipes['recipe2']['input'].items()) + + def should_sell_products(self, market): + return any(market.products[product] >= 2 * self.average_prices[product] for product in self.inventory if self.inventory[product] > 0) + + def sell_high_value_products(self, market): for product, quantity in self.inventory.items(): - if quantity > 0: - self.trade_product(self._market, product, quantity, buying=False) - self.last_action = f"Sold {quantity} of {product}" - break + if quantity > 0 and market.products[product] >= 2 * self.average_prices[product]: + self.trade_product(market, product, quantity, buying=False) + action = f"Sold high value products" + self.actions_history.append(action) - def buy_inventory_items(self): - """Executes the buying of inventory items based on current market prices.""" - for product, price in self._market.products.items(): - if price > 0: - quantity = int(self.cash // price) - if quantity > 0: - self.trade_product(self._market, product, quantity) - self.last_action = f"Bought {quantity} of {product}" - break - - def perform_stock_action(self, action): - """Performs a stock action, buying or selling, based on a selected strategy.""" - company_id, amount = self.select_stock_action(action) - if company_id: - self.trade_stock(action, self._market, company_id, amount, is_ai=True) - - def select_stock_action(self, action): - """Selects a stock action to perform, either buying or selling.""" - if action == 'buy': - return self.select_stock_to_buy() - elif action == 'sell': - return self.select_stock_to_sell() - return None, 0 - - def select_stock_to_buy(self): - """Chooses a company's stock to buy based on market conditions.""" - company_id = random.choice(list(self._market.companies.keys())) - amount = random.randint(1, 10) - return company_id, amount - - def select_stock_to_sell(self): - """Chooses a company's stock to sell from the AI's holdings.""" - owned_stocks = [(comp_id, amount) for comp_id, amount in self.stock_holdings.items() if amount > 0] - if owned_stocks: - company_id, _ = random.choice(owned_stocks) - amount = random.randint(1, self.stock_holdings[company_id]) - return company_id, amount - return None, 0 - - def attempt_crafting(self): - """Attempts to craft an item based on the best available crafting option.""" - recipe_key = self.choose_best_crafting_option() - if recipe_key: - self.craft_product(recipe_key) - self.last_action = f"Started crafting {recipe_key}" - - -class RationalAI(AICompany): - """AI strategy focused on rational and market-condition-based decisions.""" - def __init__(self, player_id, competitors_ids, market): - super().__init__(player_id, competitors_ids, market) - - def make_decision(self, market, competitors): - """Makes a market decision based on current market conditions and AI strategy.""" - if self.should_craft(): - self.attempt_crafting() - elif self.is_market_boom() or market.current_turn > 7: - self.perform_trade_action('sell') + def buy_and_craft(self, market): + chosen_recipe = crafting_recipes['recipe2'] + if all(self.inventory[product] >= qty for product, qty in chosen_recipe['input'].items()): + self.craft_product('recipe2') + print(f"Crafting using recipe2") + action = "Crafted products using recipe2" + self.actions_history.append(action) else: - self.perform_trade_action('buy') - self.perform_stock_action(random.choice(['buy', 'sell'])) - - def should_craft(self): - """Determines if crafting is a rational choice based on market conditions.""" - return random.choice([True, False]) if self.is_market_boom() else self._market.current_turn <= 7 - - -class RiskTakingAI(AICompany): - """AI strategy focused on high-risk, high-reward decisions in the market.""" - def __init__(self, player_id, competitors_ids, market): - super().__init__(player_id, competitors_ids, market) - - def make_decision(self, market, competitors): - """Makes bold market decisions based on a high-risk approach.""" - if random.choice([True, False]): - self.perform_trade_action('buy') - elif self.should_craft(): - self.attempt_crafting() - else: - self.perform_trade_action('sell') - self.perform_stock_action(random.choice(['buy', 'sell'])) - - def should_craft(self): - """Determines if crafting is a suitable choice, leaning towards riskier decisions.""" - return random.choice([True, False]) if self.is_market_boom() else self._market.current_turn <= 7 + print("Not enough resources to craft using recipe2") def print_ai_actions(ai_competitors): - """Displays the last actions taken by AI competitors in a styled console format.""" + """Displays the actions history, current cash, and inventory of AI competitors.""" console = Console() for ai in ai_competitors: - action_text = Text() + panel_text = Text() + inventory_text = ', '.join([f"{product}: {quantity}" for product, quantity in ai.inventory.items()]) - if "Bought" in ai.last_action or "Sold" in ai.last_action: - action_color = "green" if "Bought" in ai.last_action else "red" - action_text.append(ai.last_action, style=f"bold {action_color}") - elif "Crafting" in ai.last_action: - action_text.append(ai.last_action, style="bold blue") - else: - action_text.append(ai.last_action, style="bold") + # List all actions + for idx, action in enumerate(ai.actions_history, 1): + panel_text.append(f"{idx}. Action: {action}\n", style="bold cyan") - console.print( - Panel(action_text, title=f"[bold]{ai.player_id}'s Action", border_style="bright_yellow")) + # Append current cash and inventory to the text + panel_text.append(f"\nCurrent Cash: {ai.cash:.2f} €\n", style="bold yellow") + panel_text.append(f"Inventory: {inventory_text}", style="bold magenta") + + # Display in a panel + console.print(Panel(panel_text, title=f"[bold]{ai.player_id}'s Status", border_style="grey50")) -def main(): - """ - Simulates a 10-turn market game between the player and two AI competitors. - """ - competitors_ids = ["Player", "RationalAI", "RiskTakingAI"] - market = Market() - player = Company("Player", [company_type for company_type in competitors_ids if company_type != "Player"], market) - - other_competitors = set(competitors_ids) - {"RationalAI", "RiskTakingAI"} - ai_competitors = [ - RationalAI("RationalAI", list(other_competitors), market), - RiskTakingAI("RiskTakingAI", list(other_competitors), market), - ] - - market.companies = {"Player": player, "RationalAI": ai_competitors[0], "RiskTakingAI": ai_competitors[1]} - - for turn in range(10): - print(f"\n---\n") - market.update_market() - player.update_crafting() - player.make_decision(market, ai_competitors) - print(f"\n---\n") - - for ai in ai_competitors: - ai.make_decision(market, ai_competitors) - ai.update_crafting() - - print_ai_actions(ai_competitors) - - print(f"\n---\n") - final_scores = market.calculate_final_scores() - - console = Console() - score_table = Table(header_style="bold magenta", box=box.DOUBLE_EDGE) - score_table.add_column("Company", style="bold cyan") - score_table.add_column("Final Score", justify="right", style="bold yellow") - score_table.add_column("Majority Owner", style="bold green") +def display_final_scores(console, final_scores, market): + """Displays the final scores in a styled table.""" + score_table = Table(header_style="bold cyan", box=box.DOUBLE_EDGE) + score_table.add_column("Company", style="bold") + score_table.add_column("Final Score", justify="right") + score_table.add_column("Majority Owner", style="bold") score_table.add_column("Ownership Percentage", justify="right", style="dim") - sorted_scores = sorted(final_scores.items(), key=lambda item: item[1]['score'], reverse=True) - for company_id, data in sorted_scores: + for company_id, data in sorted(final_scores.items(), key=lambda item: item[1]['score'], reverse=True): ownership_percentage = f"{data['score'] / market.companies[company_id].value * 100:.2f}%" score_table.add_row(company_id, f"{data['score']:.0f}", data['majority_owner'], ownership_percentage)