- Docstrings added

- Various bug fixes and code optimisations
This commit is contained in:
Isaak Buslovich 2023-11-18 17:29:17 +01:00
parent 4add18930d
commit 84c4310c78
Signed by: Isaak
GPG Key ID: EEC31D6437FBCC63

433
main.py
View File

@ -1,3 +1,27 @@
"""
A simple business simulation game with a focus on market dynamics, crafting and trading.
Key features:
- Market simulation: Manage dynamic product prices, economic factors and random events.
- Business management: Allows players to craft products, trade and manage inventory.
- AI Competitors: Includes RationalAI and RiskTakingAI, each with unique strategies.
- Crafting System: Allows the creation of products using specific recipes.
- Stock Market: Facilitates stock trading for players and AI, affecting financial strategies.
- Turn-based gameplay: Includes crafting, trading and stock decisions per turn.
Goals:
- Engage players in strategic economic decisions in a compact, interactive format.
- Utilises a 'rich' library for enhanced console performance and interaction.
How to play:
1. Start the game by running the main function.
2. Decide on crafting, trading and stock transactions.
3. Compete against the AI with different strategies.
4. Play through a set number of rounds, resulting in a final score.
Note: Requires 'rich' and 'json' libraries for functionality.
"""
import json import json
import random import random
@ -6,6 +30,9 @@ from rich.table import Table
from rich.prompt import Prompt from rich.prompt import Prompt
from rich import box from rich import box
from rich.panel import Panel
from rich.text import Text
crafting_recipes = { crafting_recipes = {
'recipe1': {'input': {'A': 4, 'B': 4}, 'output': {'C': 10}, 'turns': 3}, 'recipe1': {'input': {'A': 4, 'B': 4}, 'output': {'C': 10}, 'turns': 3},
'recipe2': {'input': {'A': 1, 'B': 1}, 'output': {'C': 2}, 'turns': 2} 'recipe2': {'input': {'A': 1, 'B': 1}, 'output': {'C': 2}, 'turns': 2}
@ -13,6 +40,7 @@ crafting_recipes = {
def load_json(filename): def load_json(filename):
"""Loads JSON data from a file."""
try: try:
with open(filename) as file: with open(filename) as file:
return json.load(file) return json.load(file)
@ -22,6 +50,8 @@ def load_json(filename):
class Market: class Market:
"""Manages market dynamics, including companies, products, stock transactions, and economic indicators."""
def __init__(self): def __init__(self):
self.current_turn = 0 self.current_turn = 0
self.companies = {} self.companies = {}
@ -30,7 +60,7 @@ class Market:
self.adjust_prices = lambda adjustment: {k: max(1, v + adjustment) for k, v in self.products.items()} 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, 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)} "decrease": lambda x: max(1, x - 3.0)}
self.stock_ledger = {} # Initialize the stock ledger self.stock_ledger = {}
# Economic indicators # Economic indicators
self.inflation_rate = 0.02 # Example starting inflation rate (2%) self.inflation_rate = 0.02 # Example starting inflation rate (2%)
@ -38,21 +68,25 @@ class Market:
self.gdp = 500000 # Example starting GDP value self.gdp = 500000 # Example starting GDP value
def update_stock_ledger(self, company_id, owner_id, amount): def update_stock_ledger(self, company_id, owner_id, amount):
"""Updates the ledger with the given stock transaction details."""
key = (company_id, owner_id) key = (company_id, owner_id)
self.stock_ledger[key] = self.stock_ledger.get(key, 0) + amount self.stock_ledger[key] = self.stock_ledger.get(key, 0) + amount
def get_stock_ownership(self, company_id, owner_id): 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) return self.stock_ledger.get((company_id, owner_id), 0)
def get_stock_price(self, company_id): def get_stock_price(self, company_id):
company_value = self.companies[company_id].get_value(self) """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(company_value / 100.0, 2)
def update_market(self): def update_market(self):
"""Applies market events, adjusts prices, and updates economic indicators for the current turn."""
self.current_turn += 1 self.current_turn += 1
trend_adjustment = random.choice([2, -2, 0]) trend_adjustment = random.choice([2, -2, 0])
# Adjust prices based on inflation
self.products = self.adjust_prices(trend_adjustment) self.products = self.adjust_prices(trend_adjustment)
event = random.choices(self.events, weights=[e["probability"] for e in self.events])[0] event = random.choices(self.events, weights=[e["probability"] for e in self.events])[0]
@ -61,36 +95,34 @@ class Market:
else: else:
self.products = {k: self.event_effects[event["effect"]](v) for k, v in self.products.items()} self.products = {k: self.event_effects[event["effect"]](v) for k, v in self.products.items()}
self.update_economic_indicators() # Update economic indicators each turn self.update_economic_indicators()
def adjust_prices(self): def adjust_prices(self):
# Apply inflation more subtly and cumulatively """Adjusts market product prices based on inflation and other factors."""
for product, price in self.products.items(): for product, price in self.products.items():
# Apply inflation
inflation_adjustment = price * self.inflation_rate inflation_adjustment = price * self.inflation_rate
new_price = price + inflation_adjustment new_price = price + inflation_adjustment
# Apply a very small random fluctuation for additional realism # small random fluctuation for realism
fluctuation = random.uniform(-0.03, 0.03) # Slightly smaller range fluctuation = random.uniform(-0.03, 0.03)
new_price += fluctuation new_price += fluctuation
# Ensure the price doesn't fall below a reasonable minimum (e.g., 1) new_price = max(1.5, new_price)
new_price = max(1, new_price)
# Update the price with rounding off to 2 decimal places
self.products[product] = round(new_price, 2) self.products[product] = round(new_price, 2)
return self.products return self.products
def handle_competitor_event(self, effect): def handle_competitor_event(self, effect):
adjustment = random.randint(1, 3) """Handles market changes due to new or exiting competitors."""
self.products = {k: max(1, v - adjustment) if effect == "new_competitor" else v + adjustment for k, v in 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()} self.products.items()}
def update_economic_indicators(self): def update_economic_indicators(self):
"""Updates market's economic indicators like inflation and unemployment rates."""
# Update the inflation rate based on market conditions # Update the inflation rate based on market conditions
self.inflation_rate += random.uniform(-0.01, 0.01) # Random fluctuation self.inflation_rate += random.uniform(-0.01, 0.01) # Random fluctuation
self.inflation_rate = max(0, self.inflation_rate) # Ensure non-negative self.inflation_rate = max(0.5, self.inflation_rate) # Ensure non-negative
# Update the unemployment rate # Update the unemployment rate
self.unemployment_rate += random.uniform(-0.005, 0.005) # Random fluctuation self.unemployment_rate += random.uniform(-0.005, 0.005) # Random fluctuation
@ -100,9 +132,11 @@ class Market:
self.gdp += self.gdp * (random.uniform(-0.01, 0.03) + self.inflation_rate) self.gdp += self.gdp * (random.uniform(-0.01, 0.03) + self.inflation_rate)
def calculate_final_scores(self): def calculate_final_scores(self):
"""Calculates and returns final scores based on company values and stock ownership."""
final_scores = {} final_scores = {}
for company_id, company in self.companies.items(): for company_id, company in self.companies.items():
final_score = company.get_value(self) # Use the 'value' property instead of the 'get_value' method
final_score = company.value
# Determine the majority owner # Determine the majority owner
majority_owner = max(company.own_stock_ownership, majority_owner = max(company.own_stock_ownership,
@ -114,14 +148,14 @@ class Market:
# Initialize or update the score # Initialize or update the score
if company_id not in final_scores: if company_id not in final_scores:
final_scores[company_id] = {'score': 0, 'note': ''} final_scores[company_id] = {'score': 0, 'note': '', 'majority_owner': majority_owner}
# Distribute scores # Distribute scores
for owner_id, percentage in company.own_stock_ownership.items(): for owner_id, percentage in company.own_stock_ownership.items():
if percentage > 20: if percentage > 20:
score_transfer = final_score * (percentage / 100) score_transfer = final_score * (percentage / 100)
if owner_id not in final_scores: if owner_id not in final_scores:
final_scores[owner_id] = {'score': 0, 'note': ''} final_scores[owner_id] = {'score': 0, 'note': '', 'majority_owner': ''}
final_scores[owner_id]['score'] += score_transfer final_scores[owner_id]['score'] += score_transfer
# If no one owns 51% or more, assign the remainder to the company itself # If no one owns 51% or more, assign the remainder to the company itself
@ -130,50 +164,31 @@ class Market:
final_scores.get(owner, {'score': 0})['score'] for owner in company.own_stock_ownership) final_scores.get(owner, {'score': 0})['score'] for owner in company.own_stock_ownership)
final_scores[company_id]['score'] += remaining_score final_scores[company_id]['score'] += remaining_score
# Add a note about the majority owner
final_scores[company_id]['note'] = f"Score has been added to {majority_owner}"
return final_scores return final_scores
class Company: class Company:
def __init__(self, player_id, competitors_ids): """Represents a company within the market, managing finances, inventory, and stock transactions."""
def __init__(self, player_id, competitors_ids, market=None):
self.player_id = player_id self.player_id = player_id
self.cash = 500.0 self.cash = 500.0
self.inventory = {'A': 0, 'B': 0, 'C': 0} self.inventory = {'A': 0, 'B': 0, 'C': 0}
self.crafting_queue = [] self.crafting_queue = []
self.own_stock_ownership = {cid: 51 if cid == player_id else 0 for cid in [player_id] + competitors_ids}
# Initialize stock ownership in the player's company all_company_ids = set([player_id] + competitors_ids + ["RationalAI", "RiskTakingAI"])
self.own_stock_ownership = {player_id: 51} # Player owns 51 shares of their company self.other_stock_holdings = {cid: 0 for cid in all_company_ids}
for cid in competitors_ids:
self.own_stock_ownership[cid] = 0 # Competitors initially own no shares
# Initialize stock holdings in other companies self.total_shares = 100
all_companies = competitors_ids + [player_id] self._market = market
self.other_stock_holdings = {cid: 0 for cid in all_companies} # Player owns no shares in other companies initially
self.total_shares = 100 # Total shares per company @property
def value(self):
def get_value(self, market): """Calculates the total value of the company, combining cash and the market value of its inventory."""
return self.cash + sum(self.inventory[product] * price for product, price in market.products.items()) return self.cash + sum(self.inventory[product] * price for product, price in self._market.products.items())
def trade_product(self, market, product, quantity, buying):
total_cost = round(market.products[product] * quantity, 2)
print("\nProduct Trading Decision")
products = list(market.products.keys())
print("\nAvailable Products:")
for idx, product in enumerate(products, 1):
print(f" {idx}: {product}")
product_choice = self.get_user_choice(len(products), "Choose a product to trade: ")
product = products[product_choice - 1]
quantity = self.get_valid_input("Enter the quantity to trade: ", int, "Quantity must be positive.",
lambda x: x > 0)
trade_type = self.get_user_choice(2, "Choose trade type (1: Buy, 2: Sell): ")
self.trade_product(market, product, quantity, buying=trade_type == 1)
def crafting_decision(self): def crafting_decision(self):
"""Displays crafting options and handles the user's crafting choice."""
print("\nCrafting Decision") print("\nCrafting Decision")
recipe_keys = list(crafting_recipes.keys()) recipe_keys = list(crafting_recipes.keys())
print("\nAvailable Recipes:") print("\nAvailable Recipes:")
@ -182,117 +197,127 @@ class Company:
recipe_choice = self.get_user_choice(len(recipe_keys), "Choose a recipe to craft: ") recipe_choice = self.get_user_choice(len(recipe_keys), "Choose a recipe to craft: ")
self.craft_product(recipe_keys[recipe_choice - 1]) self.craft_product(recipe_keys[recipe_choice - 1])
def trade_product(self, market, product, quantity, buying): def trade_product(self, market, product, quantity, buying=True):
"""Executes a product trade transaction based on the specified parameters."""
total_cost = market.products[product] * quantity total_cost = market.products[product] * quantity
if buying: if buying and self.cash >= total_cost:
if self.cash >= total_cost:
self.cash -= total_cost self.cash -= total_cost
self.inventory[product] += quantity self.inventory[product] += quantity
elif self.inventory[product] >= quantity: elif not buying and self.inventory[product] >= quantity:
self.cash += total_cost self.cash += total_cost
self.inventory[product] -= quantity self.inventory[product] -= quantity
def craft_product(self, recipe_key): def craft_product(self, recipe_key):
"""Processes crafting of a product based on the chosen recipe."""
recipe = crafting_recipes[recipe_key] recipe = crafting_recipes[recipe_key]
if all(self.inventory[product] >= quantity for product, quantity in recipe['input'].items()): if all(self.inventory[product] >= quantity for product, quantity in recipe['input'].items()):
self.crafting_queue.append({'recipe': recipe, 'turns_remaining': recipe['turns']})
for product, quantity in recipe['input'].items(): for product, quantity in recipe['input'].items():
self.inventory[product] -= quantity self.inventory[product] -= quantity
self.crafting_queue.append({'recipe': recipe, 'turns_remaining': recipe['turns']})
print("Crafting order placed.") print("Crafting order placed.")
else: else:
print("Not enough resources to craft.") print("Not enough resources to craft.")
def update_crafting(self): def update_crafting(self):
completed_orders = [] """Updates the crafting queue, completing orders as their turns conclude."""
for order in self.crafting_queue: for order in self.crafting_queue[:]:
order['turns_remaining'] -= 1 order['turns_remaining'] -= 1
if order['turns_remaining'] == 0: if order['turns_remaining'] == 0:
for product, quantity in order['recipe']['output'].items(): for product, quantity in order['recipe']['output'].items():
self.inventory[product] += quantity self.inventory[product] += quantity
completed_orders.append(order) self.crafting_queue.remove(order)
self.crafting_queue = [order for order in self.crafting_queue if order not in completed_orders]
def trade_stock(self, action, market, company_id, amount): def trade_stock(self, action, market, company_id, amount, is_ai=False):
"""Executes a stock trade action, buying or selling as specified."""
if company_id not in market.companies and company_id != self.player_id: if company_id not in market.companies and company_id != self.player_id:
print("Company not found in the market.") return "Company not found in the market."
return
stock_price = market.get_stock_price(company_id) stock_price = market.get_stock_price(company_id)
total_value = stock_price * amount total_value = stock_price * amount
if action == 'buy': if action == 'buy':
if company_id == self.player_id: return self._buy_stock(company_id, amount, total_value, is_ai)
# Calculate available shares in the player's company elif action == 'sell':
available_shares = self.total_shares - sum(self.own_stock_ownership.values()) + \ return self._sell_stock(company_id, amount, total_value, is_ai)
self.own_stock_ownership[self.player_id]
else: else:
# Calculate available shares in an AI company return "Invalid stock action."
available_shares = self.total_shares - self.other_stock_holdings[company_id]
def _buy_stock(self, company_id, amount, total_value, is_ai):
"""Handles the buying of stocks for the specified company."""
available_shares = self._calculate_available_shares(company_id)
if amount > available_shares: if amount > available_shares:
print(f"Not enough available shares to buy. Available: {available_shares}") return f"Not enough available shares to buy. Available: {available_shares}"
return
if self.cash < total_value: if self.cash < total_value:
print("Insufficient funds to buy stocks.") return "Insufficient funds to buy stocks."
return
self.cash -= total_value self._update_stock_ownership(company_id, amount, total_value, buying=True)
message = f"Bought {amount} stocks of {company_id}."
if is_ai:
self.last_action = message
else:
print(message)
return message
def _sell_stock(self, company_id, amount, total_value, is_ai):
"""Handles the selling of stocks for the specified company."""
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}."
if is_ai:
self.last_action = message
else:
print(message)
return message
def _calculate_available_shares(self, company_id):
"""Calculates the number of available shares for a given company."""
if company_id == self.player_id: if company_id == self.player_id:
# Buying shares of the player's own company return self.total_shares - sum(self.own_stock_ownership.values()) + self.own_stock_ownership[self.player_id]
else:
return self.total_shares - self.other_stock_holdings.get(company_id, 0)
def _get_stock_ownership(self, company_id):
"""Retrieves the stock ownership amount for a given company."""
if company_id == self.player_id:
return self.own_stock_ownership[self.player_id]
return self.other_stock_holdings.get(company_id, 0)
def _update_stock_ownership(self, company_id, amount, total_value, buying):
"""Updates the stock ownership details after a buy or sell action."""
if company_id == self.player_id:
if buying:
self.own_stock_ownership[self.player_id] += amount self.own_stock_ownership[self.player_id] += amount
else: else:
# Buying shares of an AI company
self.other_stock_holdings[company_id] += amount
print(f"Bought {amount} stocks of {company_id}.")
elif action == 'sell':
if company_id == self.player_id:
# Selling shares of the player's own company
if self.own_stock_ownership[self.player_id] < amount:
print("Not enough stocks to sell.")
return
self.own_stock_ownership[self.player_id] -= amount self.own_stock_ownership[self.player_id] -= amount
else: else:
# Selling shares of an AI company if buying:
if self.other_stock_holdings.get(company_id, 0) < amount: self.other_stock_holdings[company_id] += amount
print("Not enough stocks to sell.") else:
return
self.other_stock_holdings[company_id] -= amount self.other_stock_holdings[company_id] -= amount
self.cash += -total_value if buying else total_value
self.cash += total_value
print(f"Sold {amount} stocks of {company_id}.")
def make_decision(self, market, competitors): def make_decision(self, market, competitors):
"""Presents decision options to the user and processes the chosen action."""
console = Console() console = Console()
# Enhanced table for player's status with Euro symbol
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 green]{self.player_id}'s Turn - Turn {market.current_turn}", box=box.ROUNDED)
status_table.add_column("Category", style="bold cyan")
status_table.add_column("Category", justify="left", style="bold cyan") status_table.add_column("Details")
status_table.add_column("Details", justify="left")
# Use colors to highlight key data
status_table.add_row("Cash", f"[bold yellow]{self.cash:.2f}") status_table.add_row("Cash", f"[bold yellow]{self.cash:.2f}")
status_table.add_row("Inventory", ', '.join( status_table.add_row("Inventory", ', '.join(
[f"[bold magenta]{item}: {quantity}" for item, quantity in self.inventory.items()])) [f"[bold magenta]{item}: {quantity}" for item, quantity in self.inventory.items()]))
status_table.add_row("Market Prices", ', '.join( status_table.add_row("Market Prices", ', '.join(
[f"[bold blue]{product}: {price:.2f}" for product, price in market.products.items()])) [f"[bold blue]{product}: {price:.2f}" for product, price in market.products.items()]))
# Display shareholders and investments with color coding
shareholders = ', '.join( shareholders = ', '.join(
[f"[bold]{company}[/]: {ownership} shares" for company, ownership in self.own_stock_ownership.items()]) [f"[bold]{company}[/]: {ownership} shares" for company, ownership in self.own_stock_ownership.items()])
status_table.add_row("Your Shareholders", shareholders) status_table.add_row("Your Shareholders", shareholders)
investments = ', '.join( investments = ', '.join(
[f"[bold]{company}[/]: {holding} shares" for company, holding in self.other_stock_holdings.items() if [f"[bold]{company}[/]: {holding} shares" for company, holding in self.other_stock_holdings.items() if
holding > 0]) holding > 0])
status_table.add_row("Your Investments", investments) status_table.add_row("Your Investments", investments)
console.print(status_table) console.print(status_table)
# Actions menu with clear choices
actions = { actions = {
"1": "Trade Products", "1": "Trade Products",
"2": "Craft", "2": "Craft",
@ -300,14 +325,9 @@ class Company:
"4": "Skip Turn" "4": "Skip Turn"
} }
# Display the choices to the player in an intuitive way
choices_display = "\n".join([f"{key}: {value}" for key, value in actions.items()]) choices_display = "\n".join([f"{key}: {value}" for key, value in actions.items()])
console.print(f"Available Actions:\n{choices_display}", style="bold") console.print(f"Available Actions:\n{choices_display}", style="bold")
# Prompt for action choice
action_choice = Prompt.ask("Choose your action", choices=list(actions.keys()), default="4") action_choice = Prompt.ask("Choose your action", choices=list(actions.keys()), default="4")
# Perform the selected action
selected_action = actions.get(action_choice, None) selected_action = actions.get(action_choice, None)
if selected_action == "Trade Products": if selected_action == "Trade Products":
@ -324,6 +344,7 @@ class Company:
console.print("[bold red]Invalid choice. Please enter a valid option.") console.print("[bold red]Invalid choice. Please enter a valid option.")
def trade_products_decision(self, market): def trade_products_decision(self, market):
"""Handles the decision-making process for trading products."""
print("\nProduct Trading Decision") print("\nProduct Trading Decision")
products = list(market.products.keys()) products = list(market.products.keys())
print("\nAvailable Products:") print("\nAvailable Products:")
@ -335,18 +356,18 @@ class Company:
quantity = self.get_valid_input("Enter the quantity to trade: ", int, "Quantity must be positive.", quantity = self.get_valid_input("Enter the quantity to trade: ", int, "Quantity must be positive.",
lambda x: x > 0) lambda x: x > 0)
trade_type = self.get_user_choice(2, "Choose trade type (1: Buy, 2: Sell): ") self.get_user_choice(2, "Choose trade type (1: Buy, 2: Sell): ")
self.trade_product(market, product, quantity, buying=trade_type == 1) self.trade_product(market, product, quantity)
def trade_stocks_decision(self, action, market, competitors): def trade_stocks_decision(self, action, market, competitors):
"""Facilitates the decision-making process for stock trading actions."""
print("\nStock Trading Decision") print("\nStock Trading Decision")
if action == 'buy': if action == 'buy':
print("Available companies to buy stocks from:") print("Available companies to buy stocks from:")
elif action == 'sell': elif action == 'sell':
print("Your stock holdings:") print("Your stock holdings:")
# Include the player's company in the list companies = competitors + [self]
companies = competitors + [self] # Assuming 'self' represents the player's company
for idx, company in enumerate(companies, 1): for idx, company in enumerate(companies, 1):
company_id = company.player_id company_id = company.player_id
@ -364,6 +385,7 @@ class Company:
@staticmethod @staticmethod
def get_user_choice(num_options, prompt): def get_user_choice(num_options, prompt):
"""Prompts the user for a choice and validates the input."""
choice = 0 choice = 0
while choice < 1 or choice > num_options: while choice < 1 or choice > num_options:
try: try:
@ -376,6 +398,7 @@ class Company:
@staticmethod @staticmethod
def get_valid_input(prompt, input_type, error_message, validation_func=lambda x: True): def get_valid_input(prompt, input_type, error_message, validation_func=lambda x: True):
"""Requests and validates user input based on specified criteria."""
while True: while True:
try: try:
value = input_type(input(prompt)) value = input_type(input(prompt))
@ -385,54 +408,76 @@ class Company:
except ValueError: except ValueError:
print(error_message) print(error_message)
def is_market_boom(self):
"""Checks if the market is booming."""
return all(price > 20 for price in self._market.products.values())
class AICompany(Company): class AICompany(Company):
def __init__(self, player_id, competitors_ids): """Represents an AI-driven company with automated trading, crafting, and stock actions."""
super().__init__(player_id, competitors_ids) def __init__(self, player_id, competitors_ids, market):
super().__init__(player_id, competitors_ids, market)
self.last_action = "None" self.last_action = "None"
self.average_prices = {'A': 10, 'B': 15, 'C': 20} self.average_prices = {product: market.products[product] for product in market.products}
def update_average_prices(self, market): def update_average_prices(self):
turn = market.current_turn """Updates the average prices of products based on the current market turn."""
for product, price in market.products.items(): 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] * (turn - 1) + price) / turn
def is_market_boom(self, market):
return all(price > 20 for price in market.products.values())
def choose_best_crafting_option(self): 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() 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) if all(self.inventory[prod] >= qty for prod, qty in recipe['input'].items())), None)
def perform_trade_action(self, market, action): def perform_trade_action(self, action):
"""Performs a trade action, either buying or selling, based on the specified action."""
if action == 'sell': if action == 'sell':
self.sell_inventory_items()
elif action == 'buy':
self.buy_inventory_items()
def sell_inventory_items(self):
"""Executes the selling of inventory items."""
for product, quantity in self.inventory.items(): for product, quantity in self.inventory.items():
if quantity > 0: if quantity > 0:
self.trade_product(market, product, quantity, False) self.trade_product(self._market, product, quantity, buying=False)
self.last_action = f"Sold {quantity} of {product}" self.last_action = f"Sold {quantity} of {product}"
break break
elif action == 'buy':
for product, price in market.products.items(): def buy_inventory_items(self):
if price > 0: # Check to avoid division by zero """Executes the buying of inventory items based on current market prices."""
quantity = self.cash // price for product, price in self._market.products.items():
if price > 0:
quantity = int(self.cash // price)
if quantity > 0: if quantity > 0:
self.trade_product(market, product, quantity, True) self.trade_product(self._market, product, quantity)
self.last_action = f"Bought {quantity} of {product}" self.last_action = f"Bought {quantity} of {product}"
break break
else:
print(f"Price for {product} is currently zero, skipping.")
def perform_stock_action(self, market, action): def perform_stock_action(self, action):
company_id, amount = self.select_stock_action(market, action) """Performs a stock action, buying or selling, based on a selected strategy."""
company_id, amount = self.select_stock_action(action)
if company_id: if company_id:
self.trade_stock(action, market, company_id, amount) self.trade_stock(action, self._market, company_id, amount, is_ai=True)
def select_stock_action(self, market, action): def select_stock_action(self, action):
"""Selects a stock action to perform, either buying or selling."""
if action == 'buy': if action == 'buy':
company_id = random.choice(list(market.companies.keys())) return self.select_stock_to_buy()
amount = random.randint(1, 10) # Random amount
return company_id, amount
elif action == 'sell': 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.other_stock_holdings.items() if amount > 0] owned_stocks = [(comp_id, amount) for comp_id, amount in self.other_stock_holdings.items() if amount > 0]
if owned_stocks: if owned_stocks:
company_id, _ = random.choice(owned_stocks) company_id, _ = random.choice(owned_stocks)
@ -441,6 +486,7 @@ class AICompany(Company):
return None, 0 return None, 0
def attempt_crafting(self): def attempt_crafting(self):
"""Attempts to craft an item based on the best available crafting option."""
recipe_key = self.choose_best_crafting_option() recipe_key = self.choose_best_crafting_option()
if recipe_key: if recipe_key:
self.craft_product(recipe_key) self.craft_product(recipe_key)
@ -448,71 +494,86 @@ class AICompany(Company):
class RationalAI(AICompany): 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): def make_decision(self, market, competitors):
# Decides on actions based on market conditions and crafting opportunities """Makes a market decision based on current market conditions and AI strategy."""
if self.should_craft(market): if self.should_craft():
self.attempt_crafting() self.attempt_crafting()
elif self.is_market_boom(market) or market.current_turn > 7: elif self.is_market_boom() or market.current_turn > 7:
self.perform_trade_action(market, 'sell') self.perform_trade_action('sell')
else: else:
self.perform_trade_action(market, 'buy') self.perform_trade_action('buy')
self.perform_stock_action(market, random.choice(['buy', 'sell'])) self.perform_stock_action(random.choice(['buy', 'sell']))
def should_craft(self, market): def should_craft(self):
# Determines whether crafting is a good choice based on market conditions """Determines if crafting is a rational choice based on market conditions."""
return not self.is_market_boom(market) and market.current_turn <= 7 return random.choice([True, False]) if self.is_market_boom() else self._market.current_turn <= 7
def attempt_crafting(self):
# Attempts to craft an item if the conditions are favorable
recipe_key = self.choose_best_crafting_option()
if recipe_key:
self.craft_product(recipe_key)
self.last_action = f"Started crafting {recipe_key}"
class RiskTakingAI(AICompany): 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): def make_decision(self, market, competitors):
# Randomly chooses to buy or sell based on market conditions or a 50-50 chance """Makes bold market decisions based on a high-risk approach."""
if random.choice([True, False]): if random.choice([True, False]):
self.perform_trade_action(market, 'buy') self.perform_trade_action('buy')
elif self.should_craft(market): elif self.should_craft():
self.attempt_crafting() self.attempt_crafting()
else: else:
self.perform_trade_action(market, 'sell') self.perform_trade_action('sell')
self.perform_stock_action(market, random.choice(['buy', 'sell'])) self.perform_stock_action(random.choice(['buy', 'sell']))
def should_craft(self, market): def should_craft(self):
# Decides whether to craft based on market conditions or randomly """Determines if crafting is a suitable choice, leaning towards riskier decisions."""
return random.choice([True, False]) if self.is_market_boom(market) else market.current_turn <= 7 return random.choice([True, False]) if self.is_market_boom() else self._market.current_turn <= 7
def attempt_crafting(self):
# Attempts to craft a product if possible
recipe_key = self.choose_best_crafting_option()
if recipe_key:
self.craft_product(recipe_key)
self.last_action = f"Started crafting {recipe_key}"
def print_ai_actions(ai_competitors): def print_ai_actions(ai_competitors):
"""Displays the last actions taken by AI competitors in a styled console format."""
console = Console()
for ai in ai_competitors: for ai in ai_competitors:
print(f"{ai.player_id} last action: {ai.last_action}") action_text = Text()
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")
console.print(
Panel(action_text, title=f"[bold]{ai.player_id}'s Action", border_style="bright_yellow"))
def main(): def main():
competitors_ids = ["Player", "Rational AI", "Risk-Taking AI"] """
market = Market() Simulates a 10-turn market game between the player and two AI competitors.
player = Company("Player", [id for id in competitors_ids if id != "Player"]) """
ai_competitors = [RationalAI("Rational AI", [id for id in competitors_ids if id != "Rational AI"]),
RiskTakingAI("Risk-Taking AI", [id for id in competitors_ids if id != "Risk-Taking AI"])]
# Initialize market with references to companies competitors_ids = ["Player", "RationalAI", "RiskTakingAI"]
market.companies = {"Player": player, "Rational AI": ai_competitors[0], "Risk-Taking AI": ai_competitors[1]} 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): for turn in range(10):
print(f"\n---\n") print(f"\n---\n")
market.update_market() market.update_market()
player.update_crafting() player.update_crafting()
player.make_decision(market, ai_competitors) player.make_decision(market, ai_competitors)
print(f"\n---\n")
for ai in ai_competitors: for ai in ai_competitors:
ai.make_decision(market, ai_competitors) ai.make_decision(market, ai_competitors)
@ -520,12 +581,22 @@ def main():
print_ai_actions(ai_competitors) print_ai_actions(ai_competitors)
# Calculate final scores based on company values and stock ownership print(f"\n---\n")
final_scores = market.calculate_final_scores() final_scores = market.calculate_final_scores()
print("\nFinal Company Values:") console = Console()
for company_id, value in final_scores.items(): score_table = Table(header_style="bold magenta", box=box.DOUBLE_EDGE)
print(f"{company_id}: {value}") 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")
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:
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)
console.print(score_table)
if __name__ == "__main__": if __name__ == "__main__":