From 047d547ee9736fab4938f6c695611f75dbaf7de3 Mon Sep 17 00:00:00 2001 From: Isaak Date: Thu, 16 Nov 2023 23:18:19 +0100 Subject: [PATCH] initial commit --- .gitignore | 240 ++++++++++++++++++++++++++++ main.py | 380 +++++++++++++++++++++++++++++++++++++++++++++ market_events.json | 24 +++ 3 files changed, 644 insertions(+) create mode 100644 .gitignore create mode 100644 main.py create mode 100644 market_events.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de67386 --- /dev/null +++ b/.gitignore @@ -0,0 +1,240 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# ---> JetBrains +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/main.py b/main.py new file mode 100644 index 0000000..5b12d47 --- /dev/null +++ b/main.py @@ -0,0 +1,380 @@ +import random +import json + +crafting_recipes = { + 'recipe1': {'input': {'A': 4, 'B': 4}, 'output': {'C': 10}, 'turns': 3}, + 'recipe2': {'input': {'A': 1, 'B': 1}, 'output': {'C': 2}, 'turns': 2} +} + + +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) + + +class Market: + def __init__(self): + self.current_turn = 0 + self.companies = {} + self.products = {'A': 10, 'B': 15, 'C': 20} + 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, + "decrease": lambda x: max(1, x - 3)} + self.stock_ledger = {} # Initialize the 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 = 10000 # Example starting GDP value + + def update_stock_ledger(self, company_id, owner_id, amount): + key = (company_id, owner_id) + self.stock_ledger[key] = self.stock_ledger.get(key, 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): + company_value = self.companies[company_id].get_value(self) + return company_value / 100 + + def update_market(self): + 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()} + print(f"Market Event: {event['name']}") + self.update_economic_indicators() # Update economic indicators each turn + + def handle_competitor_event(self, effect): + adjustment = random.randint(1, 3) + self.products = {k: max(1, v - adjustment) if effect == "new_competitor" else v + adjustment for k, v in + self.products.items()} + + def update_economic_indicators(self): + # Update the inflation rate based on market conditions + self.inflation_rate += random.uniform(-0.01, 0.01) # Random fluctuation + self.inflation_rate = max(0, 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.gdp += self.gdp * (random.uniform(-0.01, 0.03) + self.inflation_rate) + + def calculate_final_scores(self): + final_scores = {} + for company_id, company in self.companies.items(): + final_score = company.get_value(self) + + # 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': ''} + + # 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': ''} + final_scores[owner_id]['score'] += score_transfer + + # If no one owns 51% or more, assign the remainder to the company itself + 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 + + # Add a note about the majority owner + final_scores[company_id]['note'] = f"Score has been added to {majority_owner}" + + return final_scores + + +class Company: + def __init__(self, player_id, competitors_ids): + self.player_id = player_id + self.cash = 100 + self.inventory = {'A': 0, 'B': 0, 'C': 0} + self.crafting_queue = [] + # Initialize own stock ownership with player and competitors + self.own_stock_ownership = {self.player_id: 51} # Majority stock owned by the player + for cid in competitors_ids: + self.own_stock_ownership[cid] = 0 + + # Initialize other stock holdings + all_companies = competitors_ids + [player_id] + self.other_stock_holdings = {cid: 0 for cid in all_companies} + + def get_value(self, market): + return self.cash + sum(self.inventory[product] * price for product, price in market.products.items()) + + def trade_product(self, market, product, quantity, buying): + total_cost = market.products[product] * quantity + if buying: + if self.cash >= total_cost: + self.cash -= total_cost + self.inventory[product] += quantity + elif self.inventory[product] >= quantity: + self.cash += total_cost + self.inventory[product] -= quantity + + def craft_product(self, recipe_key): + recipe = crafting_recipes[recipe_key] + if all(self.inventory[product] >= quantity for product, quantity in recipe['input'].items()): + for product, quantity in recipe['input'].items(): + self.inventory[product] -= quantity + self.crafting_queue.append({'recipe': recipe, 'turns_remaining': recipe['turns']}) + print("Crafting order placed.") + else: + print("Not enough resources to craft.") + + def update_crafting(self): + completed_orders = [] + for order in self.crafting_queue: + order['turns_remaining'] -= 1 + if order['turns_remaining'] == 0: + for product, quantity in order['recipe']['output'].items(): + self.inventory[product] += quantity + completed_orders.append(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): + if company_id not in market.companies: + print("Company not found in the market.") + return + + stock_price = market.get_stock_price(company_id) + total_value = stock_price * amount + + if action == 'buy': + if self.cash < total_value: + print("Insufficient funds to buy stocks.") + return + self.cash -= total_value + self.other_stock_holdings[company_id] += amount + market.companies[company_id].own_stock_ownership[self.player_id] += amount + print(f"Bought {amount} stocks of {company_id}.") + elif action == 'sell': + if self.other_stock_holdings[company_id] < amount: + print("Not enough stocks to sell.") + return + self.cash += total_value + self.other_stock_holdings[company_id] -= amount + market.companies[company_id].own_stock_ownership[self.player_id] -= amount + print(f"Sold {amount} stocks of {company_id}.") + + def make_decision(self, market, competitors): + print(f"\n{self.player_id}'s Turn - Turn {market.current_turn}") + print(f"Cash: {self.cash}") + print(f"Inventory: {self.inventory}") + print(f"Market Prices: {market.products}") + print(f"Own Stock Ownership: {self.own_stock_ownership}") + print(f"Stock Holdings in Other Companies: {self.other_stock_holdings}") + + while True: + action = input("Choose an action (1: Trade Products, 2: Craft, 3: Trade Stocks, 4: Skip Turn): ") + + if action == '1': + self.trade_products_decision(market) + elif action == '2': + self.crafting_decision() + elif action == '3': + stock_action = input("Choose stock action (1: Buy, 2: Sell): ") + if stock_action == '1': + self.trade_stocks_decision('buy', market, competitors) + elif stock_action == '2': + self.trade_stocks_decision('sell', market, competitors) + elif action == '4': + break # Skip turn + else: + print("Invalid choice. Please enter a valid option.") + + def trade_stocks_decision(self, action, market, competitors): + print("Stock Trading Decision") + + # Common logic for displaying stock information + if action == 'buy': + print("Available companies to buy stocks from:") + elif action == 'sell': + print("Your stock holdings:") + + for competitor in competitors: + stock_info = f"{competitor.player_id}: Current stock price - {market.get_stock_price(competitor.player_id)}" + if action == 'sell' and self.other_stock_holdings[competitor.player_id] > 0: + stock_info += f", Owned: {self.other_stock_holdings[competitor.player_id]}" + print(stock_info) + + company_id = input("Enter the company ID to trade stocks: ") + if company_id not in market.companies: + print("Invalid company ID.") + return + + try: + amount = int(input("Enter the amount of stocks to trade: ")) + if amount <= 0: + raise ValueError("Stock amount must be positive.") + self.trade_stock(action, market, company_id, amount) + except ValueError as e: + print(f"Invalid amount: {e}") + + +class AICompany(Company): + def __init__(self, player_id, competitors_ids): + super().__init__(player_id, competitors_ids) + self.last_action = "None" + self.average_prices = {'A': 10, 'B': 15, 'C': 20} + + def update_average_prices(self, market): + turn = market.current_turn + for product, price in market.products.items(): + 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): + 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 perform_trade_action(self, market, action): + if action == 'sell': + for product, quantity in self.inventory.items(): + if quantity > 0: + self.trade_product(market, product, quantity, False) + self.last_action = f"Sold {quantity} of {product}" + break + elif action == 'buy': + for product, price in market.products.items(): + if price < self.average_prices[product] * 0.9: + quantity = self.cash // price + if quantity > 0: + self.trade_product(market, product, quantity, True) + self.last_action = f"Bought {quantity} of {product}" + break + + def perform_stock_action(self, market, action): + company_id, amount = self.select_stock_action(market, action) + if company_id: + self.trade_stock(action, market, company_id, amount) + + def select_stock_action(self, market, action): + if action == 'buy': + company_id = random.choice(list(market.companies.keys())) + amount = random.randint(1, 10) # Random amount + return company_id, amount + elif action == 'sell': + owned_stocks = [(comp_id, amount) for comp_id, amount in self.other_stock_holdings.items() if amount > 0] + if owned_stocks: + company_id, _ = random.choice(owned_stocks) + amount = random.randint(1, self.other_stock_holdings[company_id]) + return company_id, amount + return None, 0 + + def attempt_crafting(self): + 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): + def make_decision(self, market, competitors): + # Decides on actions based on market conditions and crafting opportunities + if self.should_craft(market): + self.attempt_crafting() + elif self.is_market_boom(market) or market.current_turn > 7: + self.perform_trade_action(market, 'sell') + else: + self.perform_trade_action(market, 'buy') + self.perform_stock_action(market, random.choice(['buy', 'sell'])) + + def should_craft(self, market): + # Determines whether crafting is a good choice based on market conditions + return not self.is_market_boom(market) and 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): + def make_decision(self, market, competitors): + # Randomly chooses to buy or sell based on market conditions or a 50-50 chance + if random.choice([True, False]): + self.perform_trade_action(market, 'buy') + elif self.should_craft(market): + self.attempt_crafting() + else: + self.perform_trade_action(market, 'sell') + self.perform_stock_action(market, random.choice(['buy', 'sell'])) + + def should_craft(self, market): + # Decides whether to craft based on market conditions or randomly + return random.choice([True, False]) if self.is_market_boom(market) else 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): + for ai in ai_competitors: + print(f"{ai.player_id} last action: {ai.last_action}") + + +def main(): + competitors_ids = ["Player", "Rational AI", "Risk-Taking AI"] + market = Market() + 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 + market.companies = {"Player": player, "Rational AI": ai_competitors[0], "Risk-Taking AI": ai_competitors[1]} + + for turn in range(10): + print(f"\n--- Turn {turn + 1} ---") + market.update_market() + player.update_crafting() + player.make_decision(market, ai_competitors) + + for ai in ai_competitors: + ai.make_decision(market, ai_competitors) + ai.update_crafting() + + print_ai_actions(ai_competitors) + + # Calculate final scores based on company values and stock ownership + final_scores = market.calculate_final_scores() + + print("\nFinal Company Values:") + for company_id, value in final_scores.items(): + print(f"{company_id}: {value}") + + +if __name__ == "__main__": + main() diff --git a/market_events.json b/market_events.json new file mode 100644 index 0000000..71e42ca --- /dev/null +++ b/market_events.json @@ -0,0 +1,24 @@ +{ + "events": [ + { + "name": "Boom in market", + "probability": 0.1, + "effect": "double" + }, + { + "name": "Market crash", + "probability": 0.05, + "effect": "halve" + }, + { + "name": "Minor market increase", + "probability": 0.2, + "effect": "increase" + }, + { + "name": "Minor market decrease", + "probability": 0.2, + "effect": "decrease" + } + ] +}