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