Browse Source

First pass at market commands

Kirk Trombley 3 years ago
parent
commit
f45ef791d7
1 changed files with 123 additions and 14 deletions
  1. 123 14
      commands/commands/rollcoin.py

+ 123 - 14
commands/commands/rollcoin.py

@@ -1,5 +1,6 @@
 from dataclasses import dataclass, field
 from typing import Optional
+import random
 
 from rollbot import as_command, initialize_data, RollbotFailure
 from rollbot.injection import Data, Sender, Config, Const, Arg
@@ -14,6 +15,7 @@ from rollbot.injection import Data, Sender, Config, Const, Arg
 #   !tip - transfer rollcoins from one person to another
 #   !gacha - insert a rollcoin (which enters the treasury), receive a random NFT
 #   !donate - split an amount and donate it to everyone
+# Market
 #   !bet - put some amount of rollcoins into the market, some portion of which will be put in the treasury, evolves market
 #   !cash - take some amount of rollcoins from the market, up to investment + the number of coins in the treasury, evolves market
 # Admin
@@ -40,9 +42,9 @@ class RollcoinWallet:
     nfts: list[str] = field(default_factory=list)
 
     def __str__(self):
-        s = f"Wallet: {self.balance} RollCoins\n"
+        s = f"Wallet: {self.balance}\n"
         if self.invested > 0 or self.investment_value > 0:
-            s += f"Invested: {self.investment_value} RollCoins (cost basis {self.invested})\n"
+            s += f"Invested: {self.investment_value}\n\t(cost basis {self.invested})\n"
         if len(self.nfts) > 0:
             s += f"NFTs:\n"
         for nft in self.nfts:
@@ -63,10 +65,15 @@ def convert_amount(amount):
     return float(amount)
 
 
+GLOBAL_STATE_KEY = "ROLLCOIN_GLOBAL_STATE"
+
 # injection values
-State = Data(RollcoinState).For(Const("ROLLCOIN_GLOBAL_STATE"), treasury=100, mining_puzzle=None, market_state=0)
+State = Data(RollcoinState).For(Const(GLOBAL_STATE_KEY), treasury=100, mining_puzzle=None, market_state=0)
 SenderWallet = Data(RollcoinWallet).For(Sender)
 WalletLookup = Config("rollcoin.wallet_names")
+MarketTransitions = Config("rollcoin.market.transitions")
+MarketMultipliers = Config("rollcoin.market.multipliers")
+MarketMessages = Config("rollcoin.market.messages")
 
 
 @as_command
@@ -76,7 +83,7 @@ def wallet(sender_wallet: SenderWallet):
 
 @as_command
 async def blockchain(wallets: Data(RollcoinWallet), wallet_lookup: WalletLookup, state: State):
-    response = f"Blockchain:\n\tTreasury contains {state.treasury} RollCoins\n\n"
+    response = f"Blockchain:\n\tTreasury: {state.treasury}\n\n"
     names = {v: k.title() for k, v in wallet_lookup.items()}
     async for (sender_id, wallet) in wallets.all():
         response += names.get(sender_id, f"Unnamed wallet {sender_id}") + ":\n"
@@ -87,28 +94,28 @@ async def blockchain(wallets: Data(RollcoinWallet), wallet_lookup: WalletLookup,
 @as_command
 async def tip(
     wallets: Data(RollcoinWallet),
-    sender_id: Sender, 
-    sender_wallet: SenderWallet, 
-    wallet_lookup: WalletLookup, 
-    target_name: Arg(0, missing_msg="You must tell me who to tip!"), 
+    sender_id: Sender,
+    sender_wallet: SenderWallet,
+    wallet_lookup: WalletLookup,
+    target_name: Arg(0, missing_msg="You must tell me who to tip!"),
     amount: Arg(1, convert=convert_amount, missing_msg="You must provide an amount to tip!", fail_msg="Could not parse {} as value"),
 ):
     if not isinstance(amount, float):
         # handle special converters
         amount = amount(sender_wallet.balance)
-    
-    if amount < 0:
-        RollbotFailure.INVALID_ARGUMENTS.raise_exc(f"Amount must be positive, not {amount}")
 
-    if amount == 0:
+    if sender_wallet.balance == 0:
         return "Sorry! You don't have any rollcoins right now - try mining!"
 
     if amount > sender_wallet.balance:
         return f"Sorry! You only have {sender_wallet.balance} RollCoins available - try mining for more!"
-    
+
+    if amount <= 0:
+        RollbotFailure.INVALID_ARGUMENTS.raise_exc(f"Amount must be positive, not {amount}")
+
     if (target_id := wallet_lookup.get(target_name.lower(), None)) is None:
         RollbotFailure.INVALID_ARGUMENTS.raise_exc(f"Cannot find wallet belonging to {target_name}")
-    
+
     target_wallet = await wallets.load_or(target_id)
     sender_wallet.balance -= amount
     target_wallet.balance += amount
@@ -116,3 +123,105 @@ async def tip(
     await wallets.save(sender_id, sender_wallet)
 
     return f"Done! {target_name} now has {target_wallet.balance} RollCoins!"
+
+
+async def evolve_market(state, transitions, multipliers, wallet_store, state_store):
+    weights = [row[state.market_state] for row in transitions]
+    state.market_state = random.choices(range(len(transitions)), weights=weights, k=1)[0]
+    # TODO increase treasury?
+    await state_store.save(GLOBAL_STATE_KEY, state)
+
+    multiplier = multipliers[state.market_state]
+    async for (wallet_id, wallet) in wallet_store.all():
+        wallet.investment_value *= multiplier
+        await wallet_store.save(wallet_id, wallet)
+
+
+@as_command
+async def bet(
+    sender_id: Sender,
+    sender_wallet: SenderWallet,
+    wallet_store: Data(RollcoinWallet),
+    state: State,
+    state_store: Data(RollcoinState),
+    transitions: MarketTransitions,
+    multipliers: MarketMultipliers,
+    messages: MarketMessages,
+    amount: Arg(0, convert=convert_amount, missing_msg="You must provide an amount to bet!", fail_msg="Could not parse {} as value"),
+):
+    if not isinstance(amount, float):
+        # handle special converters
+        amount = amount(sender_wallet.balance)
+
+    if sender_wallet.balance == 0:
+        return "Sorry! You don't have any rollcoins right now - try mining!"
+
+    if amount > sender_wallet.balance:
+        return f"Sorry! You only have {sender_wallet.balance} RollCoins available - try mining for more!"
+
+    if amount <= 0:
+        RollbotFailure.INVALID_ARGUMENTS.raise_exc(f"Amount must be positive, not {amount}")
+
+    sender_wallet.balance -= amount
+    sender_wallet.invested += amount
+    sender_wallet.investment_value += amount
+    await wallet_store.save(sender_id, sender_wallet)
+
+    await evolve_market(state, transitions, multipliers, wallet_store, state_store)
+    
+    return f"{messages[state.market_state]}\n{await wallet_store.load(sender_id)}"
+
+
+@as_command
+async def cash(
+    sender_id: Sender,
+    sender_wallet: SenderWallet,
+    wallet_store: Data(RollcoinWallet),
+    state: State,
+    state_store: Data(RollcoinState),
+    transitions: MarketTransitions,
+    multipliers: MarketMultipliers,
+    messages: MarketMessages,
+    amount: Arg(0, convert=convert_amount, missing_msg="You must provide an amount to bet!", fail_msg="Could not parse {} as value"),
+):
+    if not isinstance(amount, float):
+        # handle special converters
+        amount = amount(sender_wallet.investment_value)
+
+    if sender_wallet.investment_value == 0:
+        return "Sorry! You don't have any rollcoins invested right now - try betting!"
+
+    if amount > sender_wallet.investment_value:
+        return f"Sorry! You only have {sender_wallet.investment_value} RollCoins in the market - try betting more first!"
+
+    if amount <= 0:
+        RollbotFailure.INVALID_ARGUMENTS.raise_exc(f"Amount must be positive, not {amount}")
+
+    if amount <= sender_wallet.invested:
+        sender_wallet.balance += amount
+        sender_wallet.invested -= amount
+        sender_wallet.investment_value -= amount
+        await wallet_store.save(sender_id, sender_wallet)
+        response = "Successfully sold!"
+    elif amount <= sender_wallet.invested + state.treasury:
+        diff = amount - sender_wallet.invested
+        sender_wallet.balance += amount
+        sender_wallet.invested = 0
+        sender_wallet.investment_value -= amount
+        await wallet_store.save(sender_id, sender_wallet)
+        state.treasury -= diff
+        # market evolution will save state
+        response = f"Successfully sold! Took {diff} RollCoins from the treasury."
+    else:
+        given = sender_wallet.invested + state.treasury
+        sender_wallet.balance += given
+        sender_wallet.invested = 0
+        sender_wallet.investment_value -= given
+        await wallet_store.save(sender_id, sender_wallet)
+        state.treasury = 0
+        # market evolution will save state
+        response = f"Funny story, I'm actually out of coins! I gave you what I could, draining the treasury, which was {given}."
+
+    await evolve_market(state, transitions, multipliers, wallet_store, state_store)
+    
+    return f"{response}\n{messages[state.market_state]}\n{await wallet_store.load(sender_id)}"