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 import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from telegram.ext import run_async
|
from telegram.ext import run_async
|
||||||
from data import get_player
|
|
||||||
from random import randrange
|
from random import randrange
|
||||||
from time import time
|
from time import time
|
||||||
import logging
|
|
||||||
|
|
||||||
|
import logging
|
||||||
logger = logging.getLogger('tgmsbot.cards')
|
logger = logging.getLogger('tgmsbot.cards')
|
||||||
|
|
||||||
|
# from the main module
|
||||||
|
get_player = lambda *args, **kwargs: None
|
||||||
|
game_manager = None
|
||||||
|
|
||||||
MAX_LEVEL: int = 100
|
MAX_LEVEL: int = 100
|
||||||
MID_LEVEL: int = 80
|
MID_LEVEL: int = 80
|
||||||
LVL_UP_CARDS: int = 20
|
LVL_UP_CARDS: int = 20
|
||||||
|
@ -48,7 +51,7 @@ def _msg_users(update):
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def getperm(update, context):
|
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)
|
(from_user, reply_to_user) = _msg_users(update)
|
||||||
if not from_user:
|
if not from_user:
|
||||||
return
|
return
|
||||||
|
@ -63,7 +66,7 @@ def getperm(update, context):
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def setperm(update, context):
|
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)
|
(from_user, reply_to_user) = _msg_users(update)
|
||||||
if not from_user:
|
if not from_user:
|
||||||
return
|
return
|
||||||
|
@ -92,7 +95,7 @@ def lvlup(update, context):
|
||||||
'''
|
'''
|
||||||
use LVL_UP_CARDS cards to level up 1 lvl
|
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
|
LVLUP_TIMEOUT = 10
|
||||||
last_time = context.user_data.setdefault('lvlup_time', 0.0)
|
last_time = context.user_data.setdefault('lvlup_time', 0.0)
|
||||||
ctime = time()
|
ctime = time()
|
||||||
|
@ -149,7 +152,7 @@ def lvlup(update, context):
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def transfer_cards(update, context):
|
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)
|
(from_user, reply_to_user) = _msg_users(update)
|
||||||
if not from_user:
|
if not from_user:
|
||||||
return
|
return
|
||||||
|
@ -190,7 +193,7 @@ def transfer_cards(update, context):
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def rob_cards(update, context):
|
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
|
ROB_TIMEOUT = 10
|
||||||
last_time = context.user_data.setdefault('rob_time', 0.0)
|
last_time = context.user_data.setdefault('rob_time', 0.0)
|
||||||
ctime = time()
|
ctime = time()
|
||||||
|
@ -256,7 +259,7 @@ def rob_cards(update, context):
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def cards_lottery(update, context):
|
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
|
LOTTERY_TIMEOUT = 10
|
||||||
last_time = context.user_data.setdefault('lottery_time', 0.0)
|
last_time = context.user_data.setdefault('lottery_time', 0.0)
|
||||||
ctime = time()
|
ctime = time()
|
||||||
|
@ -281,7 +284,7 @@ def cards_lottery(update, context):
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def dist_cards(update, context):
|
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)
|
(from_user, _) = _msg_users(update)
|
||||||
if not from_user:
|
if not from_user:
|
||||||
return
|
return
|
||||||
|
@ -308,7 +311,7 @@ def dist_cards(update, context):
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def dist_cards_btn_click(update, context):
|
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
|
data = update.callback_query.data
|
||||||
user = update.callback_query.from_user
|
user = update.callback_query.from_user
|
||||||
omsg = update.callback_query.message
|
omsg = update.callback_query.message
|
||||||
|
@ -355,3 +358,62 @@ def dist_cards_btn_click(update, context):
|
||||||
rp[0] = -1
|
rp[0] = -1
|
||||||
omsg.edit_text(omsg.text_markdown + "褪裙了", parse_mode="Markdown", reply_markup=None)
|
omsg.edit_text(omsg.text_markdown + "褪裙了", parse_mode="Markdown", reply_markup=None)
|
||||||
context.job_queue.run_once(free_mem, 5)
|
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 math import log
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
import pickle
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
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 = updater.job_queue
|
||||||
job_queue.start()
|
job_queue.start()
|
||||||
|
|
||||||
|
PICKLE_FILE = 'tgmsbot.pickle'
|
||||||
|
|
||||||
KBD_MIN_INTERVAL = 0.5
|
KBD_MIN_INTERVAL = 0.5
|
||||||
KBD_DELAY_SECS = 0.5
|
KBD_DELAY_SECS = 0.5
|
||||||
|
GARBAGE_COLLECTION_INTERVAL = 86400
|
||||||
|
|
||||||
HEIGHT = 8
|
HEIGHT = 8
|
||||||
WIDTH = 8
|
WIDTH = 8
|
||||||
|
@ -70,7 +75,7 @@ def display_username(user, atuser=True, shorten=False, markdown=True):
|
||||||
name += " ({})".format(user.username)
|
name += " ({})".format(user.username)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
class Game():
|
class Saved_Game():
|
||||||
def __init__(self, board, group, creator, lives=1):
|
def __init__(self, board, group, creator, lives=1):
|
||||||
self.board = board
|
self.board = board
|
||||||
self.group = group
|
self.group = group
|
||||||
|
@ -79,7 +84,6 @@ class Game():
|
||||||
self.last_player = None
|
self.last_player = None
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
self.lock = Lock()
|
|
||||||
# timestamp of the last update keyboard action,
|
# timestamp of the last update keyboard action,
|
||||||
# it is used to calculate time gap between
|
# it is used to calculate time gap between
|
||||||
# two actions and identify unique actions.
|
# two actions and identify unique actions.
|
||||||
|
@ -112,17 +116,44 @@ class Game():
|
||||||
msg = "{}{} - {}项操作\n".format(msg, display_username(user), count)
|
msg = "{}{} - {}项操作\n".format(msg, display_username(user), count)
|
||||||
return msg
|
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:
|
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):
|
def append(self, board, board_hash, group_id, creator_id):
|
||||||
lives = int(board.mines/3)
|
lives = int(board.mines/3)
|
||||||
if lives <= 0:
|
if lives <= 0:
|
||||||
lives = 1
|
lives = 1
|
||||||
self.__games[board_hash] = Game(board, group_id, creator_id, lives=lives)
|
self.__games[board_hash] = Game(board, group_id, creator_id, lives=lives)
|
||||||
|
self.save_async()
|
||||||
def remove(self, board_hash):
|
def remove(self, board_hash):
|
||||||
board = self.get_game_from_hash(board_hash)
|
board = self.get_game_from_hash(board_hash)
|
||||||
if board:
|
if board:
|
||||||
del self.__games[board_hash]
|
self.__games.pop(board_hash)
|
||||||
|
self.save_async()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -130,6 +161,30 @@ class GameManager:
|
||||||
return self.__games.get(board_hash, None)
|
return self.__games.get(board_hash, None)
|
||||||
def count(self):
|
def count(self):
|
||||||
return len(self.__games)
|
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()
|
game_manager = GameManager()
|
||||||
|
|
||||||
|
@ -421,15 +476,18 @@ def handle_button_click(update, context):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
from cards import getperm, setperm, lvlup, transfer_cards, rob_cards, cards_lottery, dist_cards, dist_cards_btn_click
|
import cards
|
||||||
updater.dispatcher.add_handler(CommandHandler('getlvl', getperm))
|
setattr(cards, 'get_player', get_player)
|
||||||
updater.dispatcher.add_handler(CommandHandler('setlvl', setperm))
|
setattr(cards, 'game_manager', game_manager)
|
||||||
updater.dispatcher.add_handler(CommandHandler('lvlup', lvlup))
|
updater.dispatcher.add_handler(CommandHandler('getlvl', cards.getperm))
|
||||||
updater.dispatcher.add_handler(CommandHandler('transfer', transfer_cards))
|
updater.dispatcher.add_handler(CommandHandler('setlvl', cards.setperm))
|
||||||
updater.dispatcher.add_handler(CommandHandler('rob', rob_cards))
|
updater.dispatcher.add_handler(CommandHandler('lvlup', cards.lvlup))
|
||||||
updater.dispatcher.add_handler(CommandHandler('lottery', cards_lottery))
|
updater.dispatcher.add_handler(CommandHandler('transfer', cards.transfer_cards))
|
||||||
updater.dispatcher.add_handler(CommandHandler('dist', dist_cards))
|
updater.dispatcher.add_handler(CommandHandler('rob', cards.rob_cards))
|
||||||
updater.dispatcher.add_handler(CallbackQueryHandler(dist_cards_btn_click, pattern=r'dist'))
|
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))
|
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('stats', player_statistics))
|
||||||
updater.dispatcher.add_handler(CommandHandler('source', send_source))
|
updater.dispatcher.add_handler(CommandHandler('source', send_source))
|
||||||
updater.dispatcher.add_handler(CallbackQueryHandler(handle_button_click))
|
updater.dispatcher.add_handler(CallbackQueryHandler(handle_button_click))
|
||||||
|
updater.job_queue.run_repeating(game_manager.do_garbage_collection, GARBAGE_COLLECTION_INTERVAL, first=30)
|
||||||
try:
|
try:
|
||||||
updater.start_polling()
|
updater.start_polling()
|
||||||
updater.idle()
|
updater.idle()
|
||||||
finally:
|
finally:
|
||||||
|
game_manager.save()
|
||||||
|
logger.info('Game_manager saved.')
|
||||||
db.close()
|
db.close()
|
||||||
|
logger.info('DB closed.')
|
||||||
|
|
Loading…
Reference in a new issue