add more fun

This commit is contained in:
JerryXiao 2019-01-15 00:57:48 +08:00
parent a684ed6505
commit 2798a3dc22
Signed by: Jerry
GPG key ID: 9D9CE43650FF2BAA
4 changed files with 188 additions and 14 deletions

37
data.py Normal file
View file

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from peewee import *
db = SqliteDatabase('tgmsbot.db', pragmas={
'journal_mode': 'wal',
'cache_size': -32 * 1000})
class Player(Model):
user_id = IntegerField(unique=True, primary_key=True)
mines = IntegerField()
death = IntegerField()
wins = IntegerField()
restricted_until = IntegerField()
immunity_cards = IntegerField()
class Meta:
database = db
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
db.close()
@staticmethod
def db_close():
db.close()
db.connect()
db.create_tables([Player])
db.close()
def get_player(user_id):
db.connect()
player = Player.get_or_none(Player.user_id == user_id)
if player is None:
player = Player.create(user_id=user_id, mines=0, death=0, wins=0,
restricted_until=0, immunity_cards=0)
return player
else:
return player

29
data_ram.py Normal file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
pool = dict()
class Player():
def __init__(self, user_id, mines, death, wins, restricted_until, immunity_cards):
self.user_id = user_id
self.mines = mines
self.death = death
self.wins = wins
self.restricted_until = restricted_until
self.immunity_cards = immunity_cards
@staticmethod
def save():
pass
@staticmethod
def db_close():
pass
def get_player(user_id):
player = pool.get(user_id, None)
if player is None:
player = Player(user_id=user_id, mines=0, death=0, wins=0,
restricted_until=0, immunity_cards=0)
pool[user_id] = player
return player
else:
return player

View file

@ -46,6 +46,10 @@ class Board():
self.mmap = None self.mmap = None
self.moves = list() self.moves = list()
self.state = 0 # 0:not playing, 1:playing, 2:win, 3:dead self.state = 0 # 0:not playing, 1:playing, 2:win, 3:dead
# statistics
self.__op = 0
self.__is = 0
self.__3bv = 0
def __gen_map(self, first_move): def __gen_map(self, first_move):
height = self.height height = self.height
width = self.width width = self.width
@ -139,10 +143,9 @@ class Board():
self.__open(row, col) self.__open(row, col)
def gen_statistics(self): def gen_statistics(self):
if self.__op != 0:
return (self.__op, self.__is, self.__3bv)
self.__visited = np.zeros((self.height, self.width), dtype=np.int8) self.__visited = np.zeros((self.height, self.width), dtype=np.int8)
self.__op = 0
self.__is = 0
self.__3bv = 0
def scan_open(row, col): def scan_open(row, col):
self.__visited[row][col] = 1 self.__visited[row][col] = 1
for nbr_rc in self.__iter_neighbour(row, col): for nbr_rc in self.__iter_neighbour(row, col):

View file

@ -3,9 +3,12 @@
from mscore import Board, check_params from mscore import Board, check_params
from copy import deepcopy from copy import deepcopy
from telegram import InlineKeyboardMarkup, InlineKeyboardButton from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, run_async
from telegram.error import TimedOut as TimedOutError from telegram.error import TimedOut as TimedOutError
from numpy import array_equal from numpy import array_equal
# If no peewee orm is installed, try `from data_ram import get_player`
from data import get_player
from random import randint, choice
import time import time
import logging import logging
@ -32,11 +35,17 @@ WIN_TEXT_TEMPLATE = "哇所有奇怪的地方都被你打开啦…好羞羞\n" \
"地图Op {s_op} / Is {s_is} / 3bv {s_3bv}\n操作总数 {ops_count}\n" \ "地图Op {s_op} / Is {s_is} / 3bv {s_3bv}\n操作总数 {ops_count}\n" \
"统计:\n{ops_list}\n{last_player} 你要对人家负责哟/// ///\n\n" \ "统计:\n{ops_list}\n{last_player} 你要对人家负责哟/// ///\n\n" \
"用时{time}秒,超时{timeouts}\n\n" \ "用时{time}秒,超时{timeouts}\n\n" \
"{last_player} {reward}\n\n" \
"/mine 开始新游戏" "/mine 开始新游戏"
STEP_TEXT_TEMPLATE = "{last_player} 踩到了地雷!\n" \
"时间{time}秒,超时{timeouts}\n\n" \
"{last_player} {reward}\n\n" \
"生命值:({remain}/{ttl})"
LOSE_TEXT_TEMPLATE = "一道火光之后,你就在天上飞了呢…好奇怪喵\n" \ LOSE_TEXT_TEMPLATE = "一道火光之后,你就在天上飞了呢…好奇怪喵\n" \
"地图Op {s_op} / Is {s_is} / 3bv {s_3bv}\n操作总数 {ops_count}\n" \ "地图Op {s_op} / Is {s_is} / 3bv {s_3bv}\n操作总数 {ops_count}\n" \
"统计:\n{ops_list}\n{last_player} 是我们中出的叛徒!\n\n" \ "统计:\n{ops_list}\n{last_player} 是我们中出的叛徒!\n\n" \
"用时{time}秒,超时{timeouts}\n\n" \ "用时{time}秒,超时{timeouts}\n\n" \
"{last_player} {reward}\n\n" \
"/mine 开始新游戏" "/mine 开始新游戏"
@ -58,12 +67,12 @@ def display_username(user, atuser=True, shorten=False, markdown=True):
return name return name
class Game(): class Game():
def __init__(self, board, group, creator): def __init__(self, board, group, creator, lives=1):
self.board = board self.board = board
self.group = group self.group = group
self.creator = creator self.creator = creator
self.__actions = dict() self.__actions = dict()
self.__last_player = None self.last_player = None
self.start_time = time.time() self.start_time = time.time()
self.stopped = False self.stopped = False
# timestamp of the last update keyboard action, # timestamp of the last update keyboard action,
@ -72,9 +81,11 @@ class Game():
self.last_action = 0 self.last_action = 0
# number of timeout error catched # number of timeout error catched
self.timeouts = 0 self.timeouts = 0
self.lives = lives
self.ttl_lives = lives
def save_action(self, user, spot): def save_action(self, user, spot):
'''spot is supposed to be a tuple''' '''spot is supposed to be a tuple'''
self.__last_player = user self.last_player = user
if self.__actions.get(user, None): if self.__actions.get(user, None):
self.__actions[user].append(spot) self.__actions[user].append(spot)
else: else:
@ -82,11 +93,12 @@ class Game():
def actions_sum(self): def actions_sum(self):
mysum = 0 mysum = 0
for user in self.__actions: for user in self.__actions:
game_count(user)
count = len(self.__actions.get(user, list())) count = len(self.__actions.get(user, list()))
mysum += count mysum += count
return mysum return mysum
def get_last_player(self): def get_last_player(self):
return display_username(self.__last_player) return display_username(self.last_player)
def get_actions(self): def get_actions(self):
'''Convert actions into text''' '''Convert actions into text'''
msg = "" msg = ""
@ -98,7 +110,10 @@ class Game():
class GameManager: class GameManager:
__games = dict() __games = dict()
def append(self, board, board_hash, group_id, creator_id): def append(self, board, board_hash, group_id, creator_id):
self.__games[board_hash] = Game(board, 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)
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:
@ -114,10 +129,13 @@ class GameManager:
game_manager = GameManager() game_manager = GameManager()
@run_async
def send_keyboard(bot, update, args): def send_keyboard(bot, update, args):
msg = update.message msg = update.message
logger.info("Mine from {0}".format(update.message.from_user.id)) logger.info("Mine from {0}".format(update.message.from_user.id))
if check_restriction(update.message.from_user):
update.message.reply_text("爆炸这么多次还想扫雷?")
return
# create a game board # create a game board
if len(args) == 3: if len(args) == 3:
height = HEIGHT height = HEIGHT
@ -176,6 +194,69 @@ def send_status(bot, update):
count = game_manager.count() count = game_manager.count()
update.message.reply_text('当前进行的游戏: {}'.format(count)) update.message.reply_text('当前进行的游戏: {}'.format(count))
def gen_reward(user, negative=True):
''' Reward the player :) '''
# Negative rewards
def restrict_mining(player):
if player.immunity_cards >= 1:
player.immunity_cards -= 1
ret = "用去一张免疫卡,还剩{}".format(player.immunity_cards)
else:
now = int(time.time())
seconds = randint(30, 120)
player.restricted_until = now + seconds
ret = "被限制扫雷{}".format(seconds)
player.save()
return ret
# Positive rewards
def give_immunity_cards(player):
if player.immunity_cards >= 3 and choice((True, False)):
action = "没收"
player.immunity_cards -= 1
else:
action = "奖励"
player.immunity_cards += 1
player.save()
return "{}了一张免疫卡,共有{}".format(action, player.immunity_cards)
player = get_player(user.id)
if negative:
player.death += 1
return restrict_mining(player)
else:
player.wins += 1
return give_immunity_cards(player)
def game_count(user):
player = get_player(user.id)
player.mines += 1
player.save()
def check_restriction(user):
player = get_player(user.id)
player.db_close()
now = int(time.time())
if now >= player.restricted_until:
return False
else:
return player.restricted_until - now
@run_async
def player_statistics(bot, update):
logger.info("Statistics from {0}".format(update.message.from_user.id))
user = update.message.from_user
player = get_player(user.id)
player.db_close()
mines = player.mines
death = player.death
wins = player.wins
cards = player.immunity_cards
TEMPLATE = "一共玩了{mines}局,爆炸{death}次,赢了{wins}\n" \
"口袋里有{cards}张免疫卡"
update.message.reply_text(TEMPLATE.format(mines=mines, death=death,
wins=wins, cards=cards))
def update_keyboard_request(bot, bhash, game, chat_id, message_id): def update_keyboard_request(bot, bhash, game, chat_id, message_id):
current_action_timestamp = time.time() current_action_timestamp = time.time()
if current_action_timestamp - game.last_action <= KBD_MIN_INTERVAL: if current_action_timestamp - game.last_action <= KBD_MIN_INTERVAL:
@ -221,12 +302,18 @@ def update_keyboard(bot, job, noqueue=None):
logger.debug('time out in game {}.'.format(bhash)) logger.debug('time out in game {}.'.format(bhash))
game.timeouts += 1 game.timeouts += 1
@run_async
def handle_button_click(bot, update): def handle_button_click(bot, update):
msg = update.callback_query.message msg = update.callback_query.message
user = update.callback_query.from_user user = update.callback_query.from_user
chat_id = update.callback_query.message.chat.id chat_id = update.callback_query.message.chat.id
data = update.callback_query.data data = update.callback_query.data
logger.debug('Button clicked by {}, data={}.'.format(user.id, data)) logger.debug('Button clicked by {}, data={}.'.format(user.id, data))
restriction = check_restriction(user)
if restriction:
bot.answer_callback_query(callback_query_id=update.callback_query.id,
text="还有{}秒才能扫雷".format(restriction), show_alert=True)
return
bot.answer_callback_query(callback_query_id=update.callback_query.id) bot.answer_callback_query(callback_query_id=update.callback_query.id)
try: try:
data = data.split(' ') data = data.split(' ')
@ -262,17 +349,34 @@ def handle_button_click(bot, update):
last_player = game.get_last_player() last_player = game.get_last_player()
time_used = time.time() - game.start_time time_used = time.time() - game.start_time
timeouts = game.timeouts timeouts = game.timeouts
remain = 0
ttl = 0
if board.state == 2: if board.state == 2:
reward = gen_reward(game.last_player, negative=False)
template = WIN_TEXT_TEMPLATE template = WIN_TEXT_TEMPLATE
else: elif board.state == 3:
reward = gen_reward(game.last_player, negative=True)
game.lives -= 1
if game.lives <= 0:
template = LOSE_TEXT_TEMPLATE template = LOSE_TEXT_TEMPLATE
else:
game.stopped = False
board.state = 1
remain = game.lives
ttl = game.ttl_lives
template = STEP_TEXT_TEMPLATE
else:
# Should not reach here
reward = None
myreply = template.format(s_op=s_op, s_is=s_is, s_3bv=s_3bv, ops_count=ops_count, myreply = template.format(s_op=s_op, s_is=s_is, s_3bv=s_3bv, ops_count=ops_count,
ops_list=ops_list, last_player=last_player, ops_list=ops_list, last_player=last_player,
time=round(time_used, 4), timeouts=timeouts) time=round(time_used, 4), timeouts=timeouts, reward=reward,
remain=remain, ttl=ttl)
try: try:
msg.reply_text(myreply, parse_mode="Markdown") msg.reply_text(myreply, parse_mode="Markdown")
except TimedOutError: except TimedOutError:
logger.debug('timeout sending report for game {}'.format(bhash)) logger.debug('timeout sending report for game {}'.format(bhash))
if game.stopped:
game_manager.remove(bhash) game_manager.remove(bhash)
elif mmap is not None and (not array_equal(board.map, mmap)): elif mmap is not None and (not array_equal(board.map, mmap)):
game.save_action(user, (row, col)) game.save_action(user, (row, col))
@ -283,6 +387,7 @@ def handle_button_click(bot, update):
updater.dispatcher.add_handler(CommandHandler('start', send_help)) updater.dispatcher.add_handler(CommandHandler('start', send_help))
updater.dispatcher.add_handler(CommandHandler('mine', send_keyboard, pass_args=True)) updater.dispatcher.add_handler(CommandHandler('mine', send_keyboard, pass_args=True))
updater.dispatcher.add_handler(CommandHandler('status', send_status)) 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(CommandHandler('source', send_source))
updater.dispatcher.add_handler(CallbackQueryHandler(handle_button_click)) updater.dispatcher.add_handler(CallbackQueryHandler(handle_button_click))
updater.start_polling() updater.start_polling()