diff --git a/tipbot/config.py.example b/tipbot/config.py.example index a2b11cf..5a93ec2 100644 --- a/tipbot/config.py.example +++ b/tipbot/config.py.example @@ -92,3 +92,11 @@ blackjack_max_loss_ratio = 0.05 bookie_fee = 0.01 bookie_min_bet = 0.001 bookie_max_bet = 1000 + +pinata_start_amount = 0.75 +pinata_base_target = 2 +pinata_num_increments = 20 +pinata_target_increment = 0.01 +pinata_winner_base_share = 0.75 +pinata_rain_remainder_share = 0.8 +pinata_carry_remainder_share = 0.17 diff --git a/tipbot/modules/pinata.py b/tipbot/modules/pinata.py new file mode 100644 index 0000000..9da0e3c --- /dev/null +++ b/tipbot/modules/pinata.py @@ -0,0 +1,192 @@ +#!/bin/python +# +# Cryptonote tipbot - pinata commands +# Copyright 2015 moneromooo +# +# The Cryptonote tipbot is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2, or (at your option) +# any later version. +# + +import sys +import os +import redis +import hashlib +import time +import string +import random +import math +import tipbot.config as config +from tipbot.log import log_error, log_warn, log_info, log_log +import tipbot.coinspecs as coinspecs +from tipbot.utils import * +from tipbot.command_manager import * +from tipbot.redisdb import * +from tipbot.betutils import * + +def GetTarget(units): + umod = long(config.pinata_target_increment * coinspecs.atomic_units + 0.5) + units = long(float(units)+0.5) + units -= units % umod + return units + +def PreparePinata(reset=False,units=None): + p=redis_pipeline() + if reset or not redis_hexists('pinata','target'): + target=GetTarget((config.pinata_base_target+config.pinata_target_increment*random.randint(0,config.pinata_num_increments))*coinspecs.atomic_units) + log_log('PreparePinata: target %s' % target) + p.hset('pinata','target',target) + if reset or not redis_hexists('pinata','units'): + units=long(units or config.pinata_start_amount*coinspecs.atomic_units) + if units < config.pinata_start_amount*coinspecs.atomic_units: + units = long(config.pinata_start_amount*coinspecs.atomic_units) + p.hset('pinata','units',units) + p.hincrby('pinata','profit',-units) + p.hincrby('earmarked','pinata',units) + p.execute() + +def Pinata(link,cmd): + # Make sure there is always one + PreparePinata() + + identity=link.identity() + group=link.group + if not group: + link.send("There is no pinata around, they only appear in IRC channels") + return + + try: + pinata_units = long(redis_hget('pinata','units')) + except Exception,e: + log_error('Failed to get pinata units: %s' % str(e)) + link.send('An error occured') + return + + min_target=GetTarget(config.pinata_base_target*coinspecs.atomic_units) + max_target=GetTarget((config.pinata_base_target+(config.pinata_num_increments-1)*config.pinata_target_increment)*coinspecs.atomic_units) + + if not GetParam(cmd,1): + min_win_units = (pinata_units + config.pinata_base_target*coinspecs.atomic_units) * config.pinata_winner_base_share + link.send("The pinata is filled with %s, and can be hit with %s - %s (in increments of %s) - min win %s" % (AmountToString(pinata_units),AmountToString(min_target),AmountToString(max_target),AmountToString(config.pinata_target_increment*coinspecs.atomic_units),AmountToString(min_win_units))) + return + + try: + amount=float(cmd[1]) + units=long(amount*coinspecs.atomic_units+0.5) + aim = GetTarget(units) + except Exception,e: + link.send("Usage: !pinata ") + return + if aim < min_target: + link.send("The pinata can only be hit by at least %s" % AmountToString(min_target)) + return + if aim > max_target: + link.send("The pinata can only be hit by at most %s" % AmountToString(max_target)) + return + + try: + target = long(redis_hget('pinata','target')) + except Exception,e: + log_error('Failed to get pinata target: %s' % str(e)) + link.send('An error occured') + return + + if target < min_target or target > max_target: + log_error('Pinata target out of range: %s' % target) + link.send('An error occured') + return + + account = GetAccount(identity) + log_info("Pinata: %s wants to swing %s at the pinata, aim is %d, target is %d" % (identity, amount, aim, target)) + valid,reason = IsBetValid(link,amount,None,None,0,0,0) + if not valid: + log_info("Pinata: %s's bet refused: %s" % (identity, reason)) + link.send("%s: %s" % (link.user.nick, reason)) + return + + try: + if target==aim: + log_info("Pinata: %s hits the pinata containing %s" % (identity, AmountToString(pinata_units))) + + winner_ratio = config.pinata_winner_base_share * aim / min_target + rain_ratio = (1-winner_ratio) * config.pinata_rain_remainder_share + carry_ratio = (1-winner_ratio) * config.pinata_carry_remainder_share + winner_units = long(pinata_units * winner_ratio) + rain_units = long(pinata_units * rain_ratio) + carry_units = long(pinata_units * carry_ratio) + profit_units = pinata_units - winner_units - rain_units + + log_log("Pinata: %s to winner, %s to rain, %s carry" % (AmountToString(winner_units),AmountToString(rain_units),AmountToString(carry_units))) + + p=redis_pipeline() + p.hincrby('earmarked','pinata',-pinata_units) + p.hincrby('balances',account,winner_units) + link.send('%s swings at the pinata with %s and hits!' % (link.user.nick,AmountToString(units))) + link.send('%s gets splashed by %s' % (link.user.nick,AmountToString(winner_units))) + + userlist=link.network.get_users(group.name) + log_log("users in %s: %s" % (group.name,str([user.identity() for user in userlist]))) + userlist.remove(link) + for n in config.no_rain_to_nicks: + i=IdentityFromString(link,n) + l=Link(link.network,User(link.network,NickFromIdentity(i)),group) + if l in userlist: + userlist.remove(l) + + if len(userlist) > 0: + user_units = long(rain_units / len(userlist)) + log_log("The pinata rains %s on: %s" % (AmountToString(user_units),str([user.identity() for user in userlist]))) + link.send('%s rains on everyone else' % (AmountToString(user_units))) + for n in userlist: + a = GetAccount(n) + p.hincrby('balances',a,user_units) + + p.hincrby('pinata','games',1) + p.hincrby('pinata','profit',profit_units) + p.execute() + PreparePinata(True,carry_units) + link.send('A new pinata appears filled with %s!' % AmountToString(carry_units)) + else: + p=redis_pipeline() + p.hincrby('balances',account,-units) + p.hincrby('pinata','units',units) + p.hincrby('earmarked','pinata',units) + p.execute() + link.send('%s swings at the pinata with %s and misses! The pinata now contains %s' % (link.user.nick,AmountToString(units),AmountToString(pinata_units+units))) + except Exception,e: + log_error('Pinata: error: %s' % str(e)) + link.send('An error occured') + return + +def PinataHelp(link): + link.send_private("A pinata full of %s is floating in the air. Swing some %s at it!" % (coinspecs.name,coinspecs.name)) + link.send_private("This pinata can only be smashed by a secret amount of %s," % (coinspecs.name)) + min_target=GetTarget(config.pinata_base_target*coinspecs.atomic_units) + max_target=GetTarget((config.pinata_base_target+(config.pinata_num_increments-1)*config.pinata_target_increment)*coinspecs.atomic_units) + link.send_private("between %s and %s (inclusive), in %s increments" % (AmountToString(min_target),AmountToString(max_target),AmountToString(config.pinata_target_increment*coinspecs.atomic_units))) + min_winner_share=config.pinata_winner_base_share + max_winner_share=config.pinata_winner_base_share*(config.pinata_base_target+(config.pinata_num_increments-1)*config.pinata_target_increment)/config.pinata_base_target + link.send_private("If you hit with the secret amount, it breaks and you get %u%% - %u%% of the %s in it," % (100*min_winner_share,100*max_winner_share,coinspecs.name)) + link.send_private("%u%% of the rest rains down on others in the channel, and %u%% are placed" % (100*config.pinata_rain_remainder_share,100*config.pinata_carry_remainder_share)) + link.send_private("in a new pinata. If you miss it, your %s end up in the pinata," % (coinspecs.name)) + link.send_private("increasing the bounty for next attempt") + link.send_private("The winner's share is proportional to the amount of %s on the winning hit," % coinspecs.name) + link.send_private("so you don't lose out by trying higher amounts") + link.send_private("Remember, only one particular amount will manage to smash the pinata, try to find it!") + + + +random.seed(time.time()) +RegisterModule({ + 'name': __name__, + 'help': PinataHelp, +}) +RegisterCommand({ + 'module': __name__, + 'name': 'pinata', + 'parms': '', + 'function': Pinata, + 'registered': True, + 'help': "swing some %s at the pinata" % (coinspecs.name) +})