add persistence for games and /reveal
This commit is contained in:
parent
207bc43487
commit
31e8c5278c
2 changed files with 147 additions and 23 deletions
82
cards.py
82
cards.py
|
@ -3,13 +3,16 @@
|
|||
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from telegram.ext import run_async
|
||||
from data import get_player
|
||||
from random import randrange
|
||||
from time import time
|
||||
import logging
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('tgmsbot.cards')
|
||||
|
||||
# from the main module
|
||||
get_player = lambda *args, **kwargs: None
|
||||
game_manager = None
|
||||
|
||||
MAX_LEVEL: int = 100
|
||||
MID_LEVEL: int = 80
|
||||
LVL_UP_CARDS: int = 20
|
||||
|
@ -48,7 +51,7 @@ def _msg_users(update):
|
|||
|
||||
@run_async
|
||||
def getperm(update, context):
|
||||
logging.info(f'getperm from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'getperm from {getattr(update.effective_user, "id", None)}')
|
||||
(from_user, reply_to_user) = _msg_users(update)
|
||||
if not from_user:
|
||||
return
|
||||
|
@ -63,7 +66,7 @@ def getperm(update, context):
|
|||
|
||||
@run_async
|
||||
def setperm(update, context):
|
||||
logging.info(f'setperm from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'setperm from {getattr(update.effective_user, "id", None)}')
|
||||
(from_user, reply_to_user) = _msg_users(update)
|
||||
if not from_user:
|
||||
return
|
||||
|
@ -92,7 +95,7 @@ def lvlup(update, context):
|
|||
'''
|
||||
use LVL_UP_CARDS cards to level up 1 lvl
|
||||
'''
|
||||
logging.info(f'lvlup from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'lvlup from {getattr(update.effective_user, "id", None)}')
|
||||
LVLUP_TIMEOUT = 10
|
||||
last_time = context.user_data.setdefault('lvlup_time', 0.0)
|
||||
ctime = time()
|
||||
|
@ -149,7 +152,7 @@ def lvlup(update, context):
|
|||
|
||||
@run_async
|
||||
def transfer_cards(update, context):
|
||||
logging.info(f'transfer_cards from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'transfer_cards from {getattr(update.effective_user, "id", None)}')
|
||||
(from_user, reply_to_user) = _msg_users(update)
|
||||
if not from_user:
|
||||
return
|
||||
|
@ -190,7 +193,7 @@ def transfer_cards(update, context):
|
|||
|
||||
@run_async
|
||||
def rob_cards(update, context):
|
||||
logging.info(f'rob_cards from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'rob_cards from {getattr(update.effective_user, "id", None)}')
|
||||
ROB_TIMEOUT = 10
|
||||
last_time = context.user_data.setdefault('rob_time', 0.0)
|
||||
ctime = time()
|
||||
|
@ -256,7 +259,7 @@ def rob_cards(update, context):
|
|||
|
||||
@run_async
|
||||
def cards_lottery(update, context):
|
||||
logging.info(f'cards_lottery from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'cards_lottery from {getattr(update.effective_user, "id", None)}')
|
||||
LOTTERY_TIMEOUT = 10
|
||||
last_time = context.user_data.setdefault('lottery_time', 0.0)
|
||||
ctime = time()
|
||||
|
@ -281,7 +284,7 @@ def cards_lottery(update, context):
|
|||
|
||||
@run_async
|
||||
def dist_cards(update, context):
|
||||
logging.info(f'dist_cards from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'dist_cards from {getattr(update.effective_user, "id", None)}')
|
||||
(from_user, _) = _msg_users(update)
|
||||
if not from_user:
|
||||
return
|
||||
|
@ -308,7 +311,7 @@ def dist_cards(update, context):
|
|||
|
||||
@run_async
|
||||
def dist_cards_btn_click(update, context):
|
||||
logging.info(f'dist_cards_btn_click from {getattr(update.effective_user, "id", None)}')
|
||||
logger.info(f'dist_cards_btn_click from {getattr(update.effective_user, "id", None)}')
|
||||
data = update.callback_query.data
|
||||
user = update.callback_query.from_user
|
||||
omsg = update.callback_query.message
|
||||
|
@ -355,3 +358,62 @@ def dist_cards_btn_click(update, context):
|
|||
rp[0] = -1
|
||||
omsg.edit_text(omsg.text_markdown + "褪裙了", parse_mode="Markdown", reply_markup=None)
|
||||
context.job_queue.run_once(free_mem, 5)
|
||||
|
||||
@run_async
|
||||
def reveal(update, context):
|
||||
logger.info(f'reveal from {getattr(update.effective_user, "id", None)}')
|
||||
(from_user, _) = _msg_users(update)
|
||||
if not from_user:
|
||||
return
|
||||
if (msg := update.effective_message) and (rmsg := msg.reply_to_message):
|
||||
try:
|
||||
assert (rmarkup := rmsg.reply_markup) and (kbd := rmarkup.inline_keyboard) \
|
||||
and type((btn := kbd[0][0])) is InlineKeyboardButton and (data := btn.callback_data)
|
||||
data = data.split(' ')
|
||||
data = [int(i) for i in data]
|
||||
(bhash, _, _) = data
|
||||
except:
|
||||
msg.reply_text('不是一条有效的消息')
|
||||
return
|
||||
game = game_manager.get_game_from_hash(bhash)
|
||||
if not game:
|
||||
msg.reply_text('这局似乎走丢了呢')
|
||||
return
|
||||
if (mmap := game.board.mmap) is None:
|
||||
msg.reply_text('这局似乎还没开始呢')
|
||||
return
|
||||
def map_to_msg():
|
||||
ZERO_CELL = '\u23f9'
|
||||
MINE_CELL = '\u2622'
|
||||
NUM_CELL_SUFFIX = '\ufe0f\u20e3'
|
||||
BAD_CELL = "\U0001f21a\ufe0f"
|
||||
msg_text = ""
|
||||
for row in mmap:
|
||||
for cell in row:
|
||||
if cell == 0:
|
||||
msg_text += ZERO_CELL
|
||||
elif cell == 9:
|
||||
msg_text += MINE_CELL
|
||||
elif cell in range(1,9):
|
||||
msg_text += str(cell) + NUM_CELL_SUFFIX
|
||||
else:
|
||||
msg_text += BAD_CELL
|
||||
msg_text += '\n'
|
||||
return msg_text
|
||||
fplayer = get_player(int(from_user.id))
|
||||
cards = abs(fplayer.immunity_cards) / 3
|
||||
def __floating(value):
|
||||
return randrange(5000,15000)/10000 * value
|
||||
cards = __floating(cards)
|
||||
cards = int(cards) if cards > 1 else 1
|
||||
extra_text = ""
|
||||
fplayer.immunity_cards -= cards
|
||||
if fplayer.permission >= MID_LEVEL and fplayer.permission < MAX_LEVEL:
|
||||
lvl = int(randrange(100,3000)/10000 * fplayer.permission)
|
||||
lvl = lvl if lvl > 0 else 1
|
||||
fplayer.permission -= lvl
|
||||
extra_text = f", {lvl}级"
|
||||
fplayer.save()
|
||||
msg.reply_text(f'本局地图如下:\n\n{map_to_msg()}\n您用去了{cards}张卡{extra_text}')
|
||||
else:
|
||||
msg.reply_text('请回复想要查看的雷区')
|
||||
|
|
88
tgmsbot.py
88
tgmsbot.py
|
@ -12,6 +12,8 @@ from random import randint, choice, randrange
|
|||
from math import log
|
||||
from threading import Lock
|
||||
import time
|
||||
from pathlib import Path
|
||||
import pickle
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
@ -22,8 +24,11 @@ updater = Updater(token, workers=8, use_context=True)
|
|||
job_queue = updater.job_queue
|
||||
job_queue.start()
|
||||
|
||||
PICKLE_FILE = 'tgmsbot.pickle'
|
||||
|
||||
KBD_MIN_INTERVAL = 0.5
|
||||
KBD_DELAY_SECS = 0.5
|
||||
GARBAGE_COLLECTION_INTERVAL = 86400
|
||||
|
||||
HEIGHT = 8
|
||||
WIDTH = 8
|
||||
|
@ -70,7 +75,7 @@ def display_username(user, atuser=True, shorten=False, markdown=True):
|
|||
name += " ({})".format(user.username)
|
||||
return name
|
||||
|
||||
class Game():
|
||||
class Saved_Game():
|
||||
def __init__(self, board, group, creator, lives=1):
|
||||
self.board = board
|
||||
self.group = group
|
||||
|
@ -79,7 +84,6 @@ class Game():
|
|||
self.last_player = None
|
||||
self.start_time = time.time()
|
||||
self.stopped = False
|
||||
self.lock = Lock()
|
||||
# timestamp of the last update keyboard action,
|
||||
# it is used to calculate time gap between
|
||||
# two actions and identify unique actions.
|
||||
|
@ -112,17 +116,44 @@ class Game():
|
|||
msg = "{}{} - {}项操作\n".format(msg, display_username(user), count)
|
||||
return msg
|
||||
|
||||
class Game():
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'unpickle' in args:
|
||||
assert len(args) == 2 and args[0] == 'unpickle'
|
||||
self.__sg = args[1]
|
||||
else:
|
||||
self.__sg = Saved_Game(*args, **kwargs)
|
||||
self.lock = Lock()
|
||||
def pickle(self):
|
||||
return self.__sg
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__sg, name, None)
|
||||
|
||||
class GameManager:
|
||||
__games = dict()
|
||||
def __init__(self):
|
||||
self.__games = dict()
|
||||
self.__pf = Path(PICKLE_FILE)
|
||||
if self.__pf.exists():
|
||||
try:
|
||||
with open(self.__pf, 'rb') as fhandle:
|
||||
saved_games = pickle.load(fhandle, fix_imports=True, errors="strict")
|
||||
self.__games = {bhash: Game('unpickle', saved_games[bhash]) for bhash in saved_games}
|
||||
except Exception as err:
|
||||
logger.error(f'Unable to load pickle file, {type(err).__name__}: {err}')
|
||||
assert type(self.__games) is dict
|
||||
for board_hash in self.__games:
|
||||
self.__games[board_hash].lock = Lock()
|
||||
def append(self, board, board_hash, group_id, creator_id):
|
||||
lives = int(board.mines/3)
|
||||
if lives <= 0:
|
||||
lives = 1
|
||||
self.__games[board_hash] = Game(board, group_id, creator_id, lives=lives)
|
||||
self.save_async()
|
||||
def remove(self, board_hash):
|
||||
board = self.get_game_from_hash(board_hash)
|
||||
if board:
|
||||
del self.__games[board_hash]
|
||||
self.__games.pop(board_hash)
|
||||
self.save_async()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -130,6 +161,30 @@ class GameManager:
|
|||
return self.__games.get(board_hash, None)
|
||||
def count(self):
|
||||
return len(self.__games)
|
||||
@run_async
|
||||
def save_async(self):
|
||||
self.save()
|
||||
def save(self):
|
||||
try:
|
||||
games_without_locks = {bhash: self.__games[bhash].pickle() for bhash in self.__games}
|
||||
with open(self.__pf, 'wb') as fhandle:
|
||||
pickle.dump(games_without_locks, fhandle, fix_imports=True)
|
||||
except Exception as err:
|
||||
logger.error(f'Unable to save pickle file, {type(err).__name__}: {err}')
|
||||
def do_garbage_collection(self, context):
|
||||
g_checked: int = 0
|
||||
g_freed: int = 0
|
||||
games = self.__games
|
||||
for board_hash in games:
|
||||
g_checked += 1
|
||||
gm = games[board_hash]
|
||||
start_time = getattr(gm, 'start_time', 0.0)
|
||||
if time.time() - start_time > 86400*10:
|
||||
g_freed += 1
|
||||
games.pop(board_hash)
|
||||
self.save_async()
|
||||
logger.info((f'Scheduled garbage collection checked {g_checked} games, '
|
||||
f'freed {g_freed} games.'))
|
||||
|
||||
game_manager = GameManager()
|
||||
|
||||
|
@ -421,15 +476,18 @@ def handle_button_click(update, context):
|
|||
raise
|
||||
|
||||
|
||||
from cards import getperm, setperm, lvlup, transfer_cards, rob_cards, cards_lottery, dist_cards, dist_cards_btn_click
|
||||
updater.dispatcher.add_handler(CommandHandler('getlvl', getperm))
|
||||
updater.dispatcher.add_handler(CommandHandler('setlvl', setperm))
|
||||
updater.dispatcher.add_handler(CommandHandler('lvlup', lvlup))
|
||||
updater.dispatcher.add_handler(CommandHandler('transfer', transfer_cards))
|
||||
updater.dispatcher.add_handler(CommandHandler('rob', rob_cards))
|
||||
updater.dispatcher.add_handler(CommandHandler('lottery', cards_lottery))
|
||||
updater.dispatcher.add_handler(CommandHandler('dist', dist_cards))
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(dist_cards_btn_click, pattern=r'dist'))
|
||||
import cards
|
||||
setattr(cards, 'get_player', get_player)
|
||||
setattr(cards, 'game_manager', game_manager)
|
||||
updater.dispatcher.add_handler(CommandHandler('getlvl', cards.getperm))
|
||||
updater.dispatcher.add_handler(CommandHandler('setlvl', cards.setperm))
|
||||
updater.dispatcher.add_handler(CommandHandler('lvlup', cards.lvlup))
|
||||
updater.dispatcher.add_handler(CommandHandler('transfer', cards.transfer_cards))
|
||||
updater.dispatcher.add_handler(CommandHandler('rob', cards.rob_cards))
|
||||
updater.dispatcher.add_handler(CommandHandler('lottery', cards.cards_lottery))
|
||||
updater.dispatcher.add_handler(CommandHandler('dist', cards.dist_cards))
|
||||
updater.dispatcher.add_handler(CommandHandler('reveal', cards.reveal))
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(cards.dist_cards_btn_click, pattern=r'dist'))
|
||||
|
||||
|
||||
updater.dispatcher.add_handler(CommandHandler('start', send_help))
|
||||
|
@ -438,8 +496,12 @@ updater.dispatcher.add_handler(CommandHandler('status', send_status))
|
|||
updater.dispatcher.add_handler(CommandHandler('stats', player_statistics))
|
||||
updater.dispatcher.add_handler(CommandHandler('source', send_source))
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(handle_button_click))
|
||||
updater.job_queue.run_repeating(game_manager.do_garbage_collection, GARBAGE_COLLECTION_INTERVAL, first=30)
|
||||
try:
|
||||
updater.start_polling()
|
||||
updater.idle()
|
||||
finally:
|
||||
game_manager.save()
|
||||
logger.info('Game_manager saved.')
|
||||
db.close()
|
||||
logger.info('DB closed.')
|
||||
|
|
Loading…
Reference in a new issue