marketui/main.py
2024-06-02 11:50:47 +02:00

665 lines
29 KiB
Python

import json
import random
import yaml
from blessed import Terminal
crafting_recipes = {
'Industrial Synthesis': {'input': {'Coal': 4, 'Copper': 4}, 'output': {'Conductor': 10}, 'turns': 3},
'Manual Synthesis': {'input': {'Coal': 1, 'Copper': 1}, 'output': {'Conductor': 2}, 'turns': 2},
'Microchip Fabrication': {'input': {'Silicon': 2, 'Conductor': 1}, 'output': {'Microchips': 5}, 'turns': 4},
'Battery Production': {'input': {'Lithium': 3, 'Copper': 2}, 'output': {'Batteries': 4}, 'turns': 3}
}
class Market:
def __init__(self):
self.current_turn = 0
self.companies = {}
self.products = {
'Coal': 10.0, 'Copper': 15.0, 'Conductor': 20.0,
'Silicon': 12.0, 'Lithium': 18.0, 'Microchips': 30.0, 'Batteries': 25.0
}
self.starting_prices = self.products.copy()
self.events = load_json('market_events.json')["events"]
self.stock_ledger = {}
self.inflation_rate = 0.05
self.inflation_growing = True
self.inflation_change = random.uniform(0.0001, 0.001)
self.gdp = 500000
self.unemployment_rate = 0.04
self.trade_volume = 0
self.total_bought = {product: 0 for product in self.products}
self.total_sold = {product: 0 for product in self.products}
self.previous_prices = self.products.copy()
self.last_event_name = None
self.trend_factor = 1.01
def update_market(self):
self.current_turn += 1
self.previous_prices = self.products.copy()
for product in self.products:
inflation_adjustment = self.inflation_rate
random_fluctuation = random.uniform(-0.01, 0.01)
price_change_factor = 1 + inflation_adjustment + random_fluctuation
self.products[product] *= price_change_factor
self.reset_trade_volumes()
self.trend_factor = self.generate_market_trend()
self.update_economic_indicators()
self.handle_market_events()
def update_prices(self):
min_prices = {
'Coal': 0.01, 'Copper': 0.20, 'Conductor': 1.50,
'Silicon': 0.50, 'Lithium': 0.50, 'Microchips': 2.00, 'Batteries': 5.00
}
max_change = 0.1
for product in self.products:
inflation_adjustment = self.inflation_rate
random_fluctuation = random.uniform(-max_change, max_change)
price_change_factor = 1 + inflation_adjustment + random_fluctuation
new_price = self.products[product] * price_change_factor
self.products[product] = max(min_prices[product], new_price)
self.reset_trade_volumes()
self.trend_factor = self.generate_market_trend()
self.update_economic_indicators()
self.handle_market_events()
def calculate_price_change(self, product):
demand_factor = self.total_bought[product] - self.total_sold[product]
return demand_factor * 0.05
def reset_trade_volumes(self):
self.total_bought = dict.fromkeys(self.total_bought, 0)
self.total_sold = dict.fromkeys(self.total_sold, 0)
def generate_market_trend(self):
trend_change = random.uniform(-0.02, 0.02)
return max(0.5, min(self.trend_factor + trend_change, 1.5))
def update_economic_indicators(self):
self.inflation_rate += self.inflation_change if self.inflation_growing else -self.inflation_change
self.inflation_rate = max(0.0001, min(self.inflation_rate, 0.05))
self.gdp *= (1 + (self.inflation_rate / 20))
self.unemployment_rate = min(max(self.unemployment_rate + random.uniform(-0.005, 0.005), 0), 1)
def handle_market_events(self):
event = random.choices(self.events, weights=[e["probability"] for e in self.events])[0]
self.last_event_name = event["name"]
getattr(self, event["effect"])() if event["effect"] in dir(self) else None
def tech_boost_effect(self):
self.products['Conductor'] *= 1.2
def resource_scarcity_effect(self):
random_resource = random.choice(['Coal', 'Copper'])
self.products[random_resource] *= 1.5
def print_market_events(self, term):
event_info = f"{term.green}Last Market Event: {self.last_event_name}{term.normal}\n"
economic_indicators = (
f"{term.cyan}Inflation Rate: {self.inflation_rate * 100:.2f}%{term.normal}\n"
f"{term.cyan}Unemployment Rate: {self.unemployment_rate * 100:.2f}%{term.normal}\n"
f"{term.cyan}GDP: {self.gdp:.2f}{term.normal}"
)
print(term.move_y(term.height // 4))
print(term.center(event_info + economic_indicators))
def update_stock_ledger(self, company_id, owner_id, 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):
return self.stock_ledger.get((company_id, owner_id), 0)
def get_stock_price(self, company_id):
return round(self.companies[company_id].value / 100.0, 2)
def simulate_supply_chain_impact(self, product):
return random.uniform(0.9, 1.1)
def record_trade(self, value):
self.trade_volume += value
def adjust_prices(self):
for product, price in self.products.items():
inflation_adjustment = price * self.inflation_rate
fluctuation = random.uniform(-0.03, 0.03)
self.products[product] = round(max(1.5, price + inflation_adjustment + fluctuation), 2)
return self.products
def handle_competitor_event(self, effect):
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 calculate_final_scores(self):
final_scores = {}
for company_id, company in self.companies.items():
final_score = company.value
majority_owner = max(company.own_stock_ownership,
key=lambda owner: (company.own_stock_ownership[owner], owner))
majority_percentage = company.own_stock_ownership[majority_owner]
is_major_owner = majority_percentage >= 51
if company_id not in final_scores:
final_scores[company_id] = {'score': 0, 'note': '', 'majority_owner': majority_owner}
for owner_id, percentage in company.own_stock_ownership.items():
if percentage > 20:
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:
def __init__(self, player_id, competitors_ids, market=None, debug=False):
self.player_id = player_id
self.cash = 500.0
self.inventory = {'Coal': 0, 'Copper': 0, 'Conductor': 0,
'Silicon': 0, 'Lithium': 0, 'Microchips': 0, 'Batteries': 0}
self.crafting_queue = []
self.own_stock_ownership = {cid: 51 if cid == player_id else 0 for cid in [player_id] + competitors_ids}
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):
return self.cash + sum(self.inventory[product] * price for product, price in self._market.products.items())
def craft_product(self, recipe_key):
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']})
self._update_inventory(recipe['input'], decrease=True)
print("Crafting order placed.")
else:
print("Not enough resources to craft.")
if self._debug:
print(f"Inventory after crafting: {self.inventory}")
def _update_inventory(self, items, decrease=False):
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
if self._debug:
print(f"Inventory after update: {self.inventory}")
def update_crafting(self):
if self._debug:
print(f"Crafting queue before update: {self.crafting_queue}")
completed_orders = []
for order in self.crafting_queue:
if self._debug:
print(f"Processing order: {order}")
order['turns_remaining'] -= 1
if order['turns_remaining'] == 0:
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)
if self._debug:
print(f"Crafting queue after update: {self.crafting_queue}")
def trade_product(self, market, product, quantity, buying=True):
total_cost = market.products[product] * quantity
if buying:
if self.cash >= total_cost:
self.cash -= total_cost
self.inventory[product] += quantity
market.total_bought[product] += quantity
market.record_trade(total_cost)
else:
print("Insufficient funds to complete purchase.")
else:
if self.inventory[product] >= quantity:
self.cash += total_cost
self.inventory[product] -= quantity
market.total_sold[product] += quantity
market.record_trade(total_cost)
else:
print("Insufficient inventory to complete sale.")
market.update_prices()
def crafting_decision(self, term):
print(term.home + term.clear + term.move_y(term.height // 2))
recipe_keys = list(crafting_recipes.keys())
recipe_choice = self.display_menu("Choose a recipe to craft:", recipe_keys, term)
self.craft_product(recipe_keys[recipe_choice])
def trade_stock(self, action, market, company_id, amount, is_ai=False):
if company_id not in market.companies and company_id != self.player_id:
return "Company not found in the market."
stock_price = market.get_stock_price(company_id)
total_value = stock_price * amount
if action == 'buy':
return self._buy_stock(company_id, amount)
elif action == 'sell':
return self._sell_stock(company_id, amount, total_value, is_ai)
else:
return "Invalid stock action."
def _buy_stock(self, company_id, amount):
total_shares_owned = sum(self._market.companies[company_id].own_stock_ownership.values())
available_shares = self.total_shares - total_shares_owned
max_affordable_shares = int(self.cash / self._market.get_stock_price(company_id))
amount = min(amount, available_shares, max_affordable_shares)
if amount == 0:
return "Cannot buy any shares due to insufficient funds or no available shares."
total_value = self._market.get_stock_price(company_id) * amount
self.cash -= total_value
self._market.update_stock_ledger(company_id, self.player_id, amount)
if company_id != self.player_id:
self.stock_holdings[company_id] += amount
else:
self.own_stock_ownership[self.player_id] += amount
return f"Bought {amount} stocks of {company_id}."
def _sell_stock(self, company_id, amount, total_value, is_ai):
if not is_ai:
if self.stock_holdings[company_id] < amount:
return "Not enough stocks to sell."
self.stock_holdings[company_id] -= amount
else:
if self.own_stock_ownership[company_id] < amount:
return "Not enough stocks to sell."
self.own_stock_ownership[company_id] -= amount
self.cash += total_value
self._market.update_stock_ledger(company_id, self.player_id, -amount)
if is_ai and company_id == self.player_id:
for shareholder in self._market.companies[company_id].own_stock_ownership.keys():
self._market.companies[company_id].own_stock_ownership[shareholder] -= amount * (
self._market.companies[company_id].own_stock_ownership[shareholder] / self.total_shares)
return f"Sold {amount} stocks of {company_id}."
def display_menu(self, prompt, options, term):
selected_option = 0
with term.cbreak(), term.hidden_cursor():
while True:
print(term.home + term.clear + term.move_y(term.height // 2))
print(term.center(prompt))
for i, option in enumerate(options):
prefix = '-> ' if i == selected_option else ' '
print(term.move_down(2) + term.center(f"{prefix}{option}"))
key = term.inkey()
if key.name == 'KEY_UP':
selected_option = (selected_option - 1) % len(options)
elif key.name == 'KEY_DOWN':
selected_option = (selected_option + 1) % len(options)
elif key.name == 'KEY_ENTER':
return selected_option
def make_decision(self, market, competitors, term):
status_info = (
f"\n{term.bold}{self.player_id}'s Turn - Turn {market.current_turn}{term.normal}\n\n"
f"Cash: {term.yellow}{self.cash:.2f}{term.normal}\n"
f"Inventory: {', '.join([f'{item}: {quantity}' for item, quantity in self.inventory.items()])}\n"
f"Market Prices: {', '.join([f'{product}: {term.green if market.products[product] > market.previous_prices[product] else term.red}{market.products[product]:.2f}{term.normal}{"" if market.products[product] > market.previous_prices[product] else ""}' for product in market.products])}\n"
f"Your Shareholders: {', '.join([f'{company}: {ownership} shares' for company, ownership in self.own_stock_ownership.items()])}\n"
f"Your Investments: {', '.join([f'{company}: {holding} shares' for company, holding in self.stock_holdings.items() if holding > 0])}"
)
actions = [
"Trade Products 📦",
"Craft 🛠️",
"Trade Stocks 📈",
"Skip Turn ⏭️"
]
selected_action = self.display_menu(status_info + "\n\nChoose your action:", actions, term)
if selected_action == 0:
self.trade_products_decision(market, term)
elif selected_action == 1:
self.crafting_decision(term)
elif selected_action == 2:
stock_actions = ["Buy 📈", "Sell 📉"]
stock_action_choice = self.display_menu("Choose stock action:", stock_actions, term)
self.trade_stocks_decision(stock_action_choice.lower(), market, competitors, term)
elif selected_action == 3:
pass
def trade_products_decision(self, market, term):
products = list(market.products.keys())
product_choice = self.display_menu("Choose a product to trade:", products, term)
product = products[product_choice]
quantity = self.get_valid_input("Enter the quantity to trade: ", int, "Quantity must be positive.",
lambda x: x > 0)
trade_types = ["Buy 📈", "Sell 📉"]
trade_choice = self.display_menu("Choose trade type:", trade_types, term)
buying = trade_choice == 0
self.trade_product(market, product, quantity, buying=buying)
def trade_stocks_decision(self, action, market, competitors, term):
companies = competitors + [self]
company_names = [company.player_id for company in companies]
company_choice = self.display_menu("Enter the company number to trade stocks:", company_names, term)
company_id = company_names[company_choice]
amount = self.get_valid_input("Enter the amount of stocks to trade: ", int, "Stock amount must be positive.",
lambda x: x > 0)
self.trade_stock(action, market, company_id, amount)
@staticmethod
def get_valid_input(prompt, input_type, error_message, validation_func=lambda x: True):
while True:
try:
value = input_type(input(prompt))
if not validation_func(value):
raise ValueError
return value
except ValueError:
print(error_message)
class AICompany(Company):
def __init__(self, player_id, competitors_ids, market, risk_tolerance):
super().__init__(player_id, competitors_ids, market)
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):
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 make_decision(self, market, competitors, term):
if self.risk_tolerance > 0.5:
self.high_risk_decision(market, term)
else:
self.low_risk_decision(market, term)
def low_risk_decision(self, market, term):
if market.current_turn == 1:
self.buy_product('Coal', self.cash / 3)
elif market.current_turn == 2:
self.buy_product('Copper', min(self.inventory['Coal'], self.cash / market.products['Copper']))
elif market.current_turn >= 3:
self.buy_and_craft()
def high_risk_decision(self, market, term):
if market.current_turn == 1:
self.sell_own_shares(market)
self.buy_product('Coal', self.cash / 2)
self.buy_product('Copper', self.cash)
else:
if self.should_craft():
self.buy_and_craft()
if self.should_sell_products(market):
self.sell_high_value_products(market)
if market.current_turn > 6:
self.buy_stocks_strategy()
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 buy_stocks_strategy(self):
own_stock_left = 100 - self.own_stock_ownership[self.player_id]
if own_stock_left > 0:
stock_price = self._market.get_stock_price(self.player_id)
amount_to_buy = min(own_stock_left, int(self.cash / stock_price))
if amount_to_buy > 0:
action = f"{self.player_id} is buying its own stock. Amount to buy: {amount_to_buy}"
self.actions_history.append(action)
self.trade_stock('buy', self._market, self.player_id, amount_to_buy, is_ai=True)
return
for company_id in self.stock_holdings:
if company_id != self.player_id:
stock_price = self._market.get_stock_price(company_id)
if stock_price <= self.cash:
amount_to_buy = int(self.cash / stock_price)
print(f"{self.player_id} is buying {company_id}'s stock. Amount to buy: {amount_to_buy}")
self.trade_stock('buy', self._market, company_id, amount_to_buy, is_ai=True)
return
def sell_own_shares(self, market):
amount_to_sell = int(self.own_stock_ownership[self.player_id] * 0.25)
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):
return all(
self.inventory[product] >= qty for product, qty in crafting_recipes['Manual Synthesis']['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 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_and_craft(self):
chosen_recipe = crafting_recipes['Manual Synthesis']
if all(self.inventory[product] >= qty for product, qty in chosen_recipe['input'].items()):
self.craft_product('Manual Synthesis')
print(f"Crafting using Manual Synthesis")
action = "Crafted products using Manual Synthesis"
self.actions_history.append(action)
else:
print("Not enough resources to craft using Manual Synthesis")
def apply_effects(player, effects, narrative_game):
for effect in effects:
for key, value in effect.items():
if key == 'employee_morale':
player.employee_morale += value
elif key == 'company_budget':
player.cash += value
elif key == 'company_reputation':
player.reputation += value
elif key == 'set_flag':
player.flags[value] = True
elif key == 'set_variable':
if value in narrative_game.scenes[narrative_game.current_scene].get('Names', {}):
narrative_game.set_random_name(
narrative_game.scenes[narrative_game.current_scene]['Names'][value], value
)
else:
narrative_game.variables[value] = value
def handle_narrative_event(player, narrative_game):
# Iterate through the scenes to find the applicable one
for scene_name, scene in narrative_game.scenes.items():
if all(player.flags.get(cond, 0) == value for cond, value in scene.get('Conditions', {}).items()):
narrative_game.current_scene = scene_name
break
# Ensure variables are set before displaying content
scene = narrative_game.scenes[narrative_game.current_scene]
for option in scene['Links']:
for effect in option['Effects']:
if 'set_variable' in effect:
variable_name = effect['set_variable']
if variable_name in scene.get('Names', {}):
narrative_game.set_random_name(scene['Names'][variable_name], variable_name)
# Display the content with variables replaced
narrative_game.display_text(narrative_game.scenes[narrative_game.current_scene]['Content'])
# Handle user input and apply effects
while True:
scene = narrative_game.scenes[narrative_game.current_scene]
if not scene['Links']:
narrative_game.display_text("The End. Thank you for playing!")
break
choice = narrative_game.handle_input(scene['Links'])
selected_option = scene['Links'][choice]
apply_effects(player, selected_option['Effects'], narrative_game)
narrative_game.current_scene = selected_option['Target']
break
class Player(Company):
def __init__(self, player_id, competitors_ids, market):
super().__init__(player_id, competitors_ids, market)
self.flags = {"daughterevent": 0}
self.variables = {}
class TUIGame:
def __init__(self, config_path):
self.term = Terminal()
self.load_config(config_path)
self.scenes = {scene['PassageName']: scene for scene in self.config}
self.current_scene = None
self.variables = {}
def load_config(self, path):
with open(path, 'r') as file:
self.config = yaml.safe_load(file)
def display_text(self, text):
text = self.replace_variables(text)
print(self.term.home + self.term.clear + self.term.move_y(self.term.height // 2))
for line in text.split('\n'):
print(self.term.center(line))
print(self.term.move_down(2) + self.term.center("Press Enter to continue..."))
with self.term.cbreak(), self.term.hidden_cursor():
while True:
key = self.term.inkey()
if key.name == 'KEY_ENTER':
break
def display_choices(self, scene, selected_choice):
content = self.replace_variables(scene['Content'])
print(self.term.home + self.term.clear + self.term.move_y(self.term.height // 2))
print(self.term.center(content))
for i, link in enumerate(scene['Links']):
prefix = '-> ' if i == selected_choice else ' '
description = link.get('EffectDescription', '')
option_text = f"{prefix}{link['Option']}"
if i == selected_choice:
print(self.term.move_down(2) + self.term.center(self.term.bold(option_text)))
print(self.term.center(self.term.gray(description)))
else:
print(self.term.move_down(2) + self.term.center(option_text))
def handle_input(self, choices):
selected_choice = 0
with self.term.cbreak(), self.term.hidden_cursor():
while True:
self.display_choices(self.scenes[self.current_scene], selected_choice)
key = self.term.inkey()
if key.name == 'KEY_UP':
selected_choice = (selected_choice - 1) % len(choices)
elif key.name == 'KEY_DOWN':
selected_choice = (selected_choice + 1) % len(choices)
elif key.name == 'KEY_ENTER':
return selected_choice
def replace_variables(self, text):
for var, value in self.variables.items():
text = text.replace(f'{{{{ {var} }}}}', value)
return text
def set_random_name(self, names_list, variable_name):
name = random.choice(names_list)
self.variables[variable_name] = name
def main():
term = Terminal()
competitors_ids = ["Player", "RationalAI", "RiskTakingAI"]
market = Market()
player = Player("Player", [cid for cid in competitors_ids if cid != "Player"], market)
player.employee_morale = 0
player.reputation = 0
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]}
narrative_game = TUIGame('narrative_scenes.yml')
for turn in range(1, 27):
print(term.home + term.clear + term.move_y(term.height // 2))
print(term.center(f"\n--- Turn {turn} ---\n"))
market.update_market()
market.print_market_events(term)
for company in [player] + ai_competitors:
company.update_crafting()
company.make_decision(market, ai_competitors, term)
handle_narrative_event(player, narrative_game)
final_scores = market.calculate_final_scores()
print_final_scores(term, final_scores, market)
def print_final_scores(term, final_scores, market):
score_table = (
f"\n{'Company':<15} {'Final Score':<15} {'Majority Owner':<20} {'Ownership Percentage':<20}\n"
f"{'-'*70}\n"
)
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 += f"{company_id:<15} {data['score']:<15.0f} {data['majority_owner']:<20} {ownership_percentage:<20}\n"
print(term.home + term.clear + term.move_y(term.height // 2))
print(term.center(score_table))
def load_json(filename):
try:
with open(filename) as file:
return json.load(file)
except FileNotFoundError:
print(f"Error: The file {filename} was not found.")
exit(1)
if __name__ == "__main__":
main()