From 2798a3dc228a8f8c472798a682189f9c8e66d453 Mon Sep 17 00:00:00 2001 From: Jerry Date: Tue, 15 Jan 2019 00:57:48 +0800 Subject: [PATCH] add more fun --- data.py | 37 +++++++++++++++ data_ram.py | 29 ++++++++++++ mscore.py | 9 ++-- tgmsbot.py | 127 +++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 data.py create mode 100644 data_ram.py diff --git a/data.py b/data.py new file mode 100644 index 0000000..461d308 --- /dev/null +++ b/data.py @@ -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 diff --git a/data_ram.py b/data_ram.py new file mode 100644 index 0000000..3759533 --- /dev/null +++ b/data_ram.py @@ -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 diff --git a/mscore.py b/mscore.py index 3124764..f1cc944 100644 --- a/mscore.py +++ b/mscore.py @@ -46,6 +46,10 @@ class Board(): self.mmap = None self.moves = list() 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): height = self.height width = self.width @@ -139,10 +143,9 @@ class Board(): self.__open(row, col) 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.__op = 0 - self.__is = 0 - self.__3bv = 0 def scan_open(row, col): self.__visited[row][col] = 1 for nbr_rc in self.__iter_neighbour(row, col): diff --git a/tgmsbot.py b/tgmsbot.py index f38ba47..3cc5bd2 100644 --- a/tgmsbot.py +++ b/tgmsbot.py @@ -3,9 +3,12 @@ from mscore import Board, check_params from copy import deepcopy 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 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 logging @@ -32,11 +35,17 @@ WIN_TEXT_TEMPLATE = "哇所有奇怪的地方都被你打开啦…好羞羞\n" \ "地图:Op {s_op} / Is {s_is} / 3bv {s_3bv}\n操作总数 {ops_count}\n" \ "统计:\n{ops_list}\n{last_player} 你要对人家负责哟/// ///\n\n" \ "用时{time}秒,超时{timeouts}次\n\n" \ + "{last_player} {reward}\n\n" \ "/mine 开始新游戏" +STEP_TEXT_TEMPLATE = "{last_player} 踩到了地雷!\n" \ + "时间{time}秒,超时{timeouts}次\n\n" \ + "{last_player} {reward}\n\n" \ + "生命值:({remain}/{ttl})" LOSE_TEXT_TEMPLATE = "一道火光之后,你就在天上飞了呢…好奇怪喵\n" \ "地图:Op {s_op} / Is {s_is} / 3bv {s_3bv}\n操作总数 {ops_count}\n" \ "统计:\n{ops_list}\n{last_player} 是我们中出的叛徒!\n\n" \ "用时{time}秒,超时{timeouts}次\n\n" \ + "{last_player} {reward}\n\n" \ "/mine 开始新游戏" @@ -58,12 +67,12 @@ def display_username(user, atuser=True, shorten=False, markdown=True): return name class Game(): - def __init__(self, board, group, creator): + def __init__(self, board, group, creator, lives=1): self.board = board self.group = group self.creator = creator self.__actions = dict() - self.__last_player = None + self.last_player = None self.start_time = time.time() self.stopped = False # timestamp of the last update keyboard action, @@ -72,9 +81,11 @@ class Game(): self.last_action = 0 # number of timeout error catched self.timeouts = 0 + self.lives = lives + self.ttl_lives = lives def save_action(self, user, spot): '''spot is supposed to be a tuple''' - self.__last_player = user + self.last_player = user if self.__actions.get(user, None): self.__actions[user].append(spot) else: @@ -82,11 +93,12 @@ class Game(): def actions_sum(self): mysum = 0 for user in self.__actions: + game_count(user) count = len(self.__actions.get(user, list())) mysum += count return mysum def get_last_player(self): - return display_username(self.__last_player) + return display_username(self.last_player) def get_actions(self): '''Convert actions into text''' msg = "" @@ -98,7 +110,10 @@ class Game(): class GameManager: __games = dict() 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): board = self.get_game_from_hash(board_hash) if board: @@ -114,10 +129,13 @@ class GameManager: game_manager = GameManager() - +@run_async def send_keyboard(bot, update, args): msg = update.message 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 if len(args) == 3: height = HEIGHT @@ -176,6 +194,69 @@ def send_status(bot, update): count = game_manager.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): current_action_timestamp = time.time() 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)) game.timeouts += 1 +@run_async def handle_button_click(bot, update): msg = update.callback_query.message user = update.callback_query.from_user chat_id = update.callback_query.message.chat.id data = update.callback_query.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) try: data = data.split(' ') @@ -262,18 +349,35 @@ def handle_button_click(bot, update): last_player = game.get_last_player() time_used = time.time() - game.start_time timeouts = game.timeouts + remain = 0 + ttl = 0 if board.state == 2: + reward = gen_reward(game.last_player, negative=False) template = WIN_TEXT_TEMPLATE + elif board.state == 3: + reward = gen_reward(game.last_player, negative=True) + game.lives -= 1 + if game.lives <= 0: + template = LOSE_TEXT_TEMPLATE + else: + game.stopped = False + board.state = 1 + remain = game.lives + ttl = game.ttl_lives + template = STEP_TEXT_TEMPLATE else: - template = LOSE_TEXT_TEMPLATE + # 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, - ops_list=ops_list, last_player=last_player, - time=round(time_used, 4), timeouts=timeouts) + ops_list=ops_list, last_player=last_player, + time=round(time_used, 4), timeouts=timeouts, reward=reward, + remain=remain, ttl=ttl) try: msg.reply_text(myreply, parse_mode="Markdown") except TimedOutError: logger.debug('timeout sending report for game {}'.format(bhash)) - game_manager.remove(bhash) + if game.stopped: + game_manager.remove(bhash) elif mmap is not None and (not array_equal(board.map, mmap)): game.save_action(user, (row, col)) update_keyboard_request(bot, bhash, game, chat_id, msg.message_id) @@ -283,6 +387,7 @@ def handle_button_click(bot, update): updater.dispatcher.add_handler(CommandHandler('start', send_help)) updater.dispatcher.add_handler(CommandHandler('mine', send_keyboard, pass_args=True)) 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.start_polling()