|
@@ -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)}"
|