mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
feat: add Battleship game with 5x5 board, ship placement and scoring
This commit is contained in:
@@ -260,7 +260,16 @@ ID: {user_info['ID']}
|
|||||||
]
|
]
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
await update.message.reply_text("🎮 Выберите игру:", reply_markup=reply_markup)
|
keyboard = [
|
||||||
|
[InlineKeyboardButton("Камень-ножницы-бумага", callback_data='game_rps')],
|
||||||
|
[InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')],
|
||||||
|
[InlineKeyboardButton("Викторина", callback_data='game_quiz')],
|
||||||
|
[InlineKeyboardButton("Морской бой", callback_data='game_battleship')],
|
||||||
|
[InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')]
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
await update.message.reply_text(GAME_MESSAGES['select_game'], reply_markup=reply_markup)
|
||||||
|
|
||||||
async def handle_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def handle_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Обработка callback запросов от инлайновых кнопок"""
|
"""Обработка callback запросов от инлайновых кнопок"""
|
||||||
@@ -279,6 +288,10 @@ ID: {user_info['ID']}
|
|||||||
await self.start_tic_tac_toe_game(query, context)
|
await self.start_tic_tac_toe_game(query, context)
|
||||||
elif query.data == 'game_quiz':
|
elif query.data == 'game_quiz':
|
||||||
await self.start_quiz_game(query, context)
|
await self.start_quiz_game(query, context)
|
||||||
|
elif query.data == 'game_battleship':
|
||||||
|
await self.start_battleship_game(query, context)
|
||||||
|
elif query.data.startswith('bs_'):
|
||||||
|
await self.handle_battleship_shot(query, context)
|
||||||
elif query.data.startswith('rps_'):
|
elif query.data.startswith('rps_'):
|
||||||
await self.handle_rps(query, context)
|
await self.handle_rps(query, context)
|
||||||
elif query.data.startswith('tictactoe_'):
|
elif query.data.startswith('tictactoe_'):
|
||||||
@@ -307,7 +320,7 @@ ID: {user_info['ID']}
|
|||||||
keyboard = self.create_tic_tac_toe_keyboard(board)
|
keyboard = self.create_tic_tac_toe_keyboard(board)
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
await query.edit_message_text("❌⭕ Крестики-нолики!\n\nВы ходите первым. Выберите клетку:", reply_markup=reply_markup)
|
await query.edit_message_text(GAME_MESSAGES['tic_tac_toe_game'], reply_markup=reply_markup)
|
||||||
|
|
||||||
async def handle_tic_tac_toe_move(self, query, context):
|
async def handle_tic_tac_toe_move(self, query, context):
|
||||||
"""Обработка ходов в игре Крестики-нолики"""
|
"""Обработка ходов в игре Крестики-нолики"""
|
||||||
@@ -317,7 +330,7 @@ ID: {user_info['ID']}
|
|||||||
return
|
return
|
||||||
|
|
||||||
if len(move_data) != 2:
|
if len(move_data) != 2:
|
||||||
await query.answer("Неверный формат хода!")
|
await query.answer(GAME_MESSAGES['invalid_move'])
|
||||||
return
|
return
|
||||||
|
|
||||||
row, col = map(int, move_data)
|
row, col = map(int, move_data)
|
||||||
@@ -326,7 +339,7 @@ ID: {user_info['ID']}
|
|||||||
turn = context.user_data.get('tictactoe_turn')
|
turn = context.user_data.get('tictactoe_turn')
|
||||||
|
|
||||||
if not board or not (0 <= row < 3 and 0 <= col < 3) or board[row][col] != ' ' or turn != 'user':
|
if not board or not (0 <= row < 3 and 0 <= col < 3) or board[row][col] != ' ' or turn != 'user':
|
||||||
await query.answer("Неверный ход!")
|
await query.answer(GAME_MESSAGES['invalid_move'])
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ход пользователя
|
# Ход пользователя
|
||||||
@@ -347,7 +360,7 @@ ID: {user_info['ID']}
|
|||||||
# Показать финальное поле с кнопками
|
# Показать финальное поле с кнопками
|
||||||
keyboard = self.create_tic_tac_toe_keyboard(board, game_over=True)
|
keyboard = self.create_tic_tac_toe_keyboard(board, game_over=True)
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
await query.edit_message_text("🎉 Вы выиграли! 🏆", reply_markup=reply_markup)
|
await query.edit_message_text(GAME_MESSAGES['tic_tac_toe_win'], reply_markup=reply_markup)
|
||||||
context.user_data.pop('tictactoe_board', None)
|
context.user_data.pop('tictactoe_board', None)
|
||||||
context.user_data.pop('tictactoe_turn', None)
|
context.user_data.pop('tictactoe_turn', None)
|
||||||
return
|
return
|
||||||
@@ -355,7 +368,7 @@ ID: {user_info['ID']}
|
|||||||
if self.is_board_full(board):
|
if self.is_board_full(board):
|
||||||
keyboard = self.create_tic_tac_toe_keyboard(board, game_over=True)
|
keyboard = self.create_tic_tac_toe_keyboard(board, game_over=True)
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
await query.edit_message_text("🤝 Ничья!", reply_markup=reply_markup)
|
await query.edit_message_text(GAME_MESSAGES['tic_tac_toe_draw'], reply_markup=reply_markup)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ход бота
|
# Ход бота
|
||||||
@@ -367,9 +380,10 @@ ID: {user_info['ID']}
|
|||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
await query.edit_message_text(GAME_MESSAGES['tic_tac_toe_lose'], reply_markup=reply_markup)
|
await query.edit_message_text(GAME_MESSAGES['tic_tac_toe_lose'], reply_markup=reply_markup)
|
||||||
return
|
return
|
||||||
|
return
|
||||||
|
|
||||||
if self.is_board_full(board):
|
if self.is_board_full(board):
|
||||||
await query.edit_message_text(GAME_MESSAGES['tic_tac_toe_draw'])
|
await query.edit_message_text(GAME_MESSAGES['tic_tac_toe_draw'], reply_markup=reply_markup)
|
||||||
return
|
return
|
||||||
|
|
||||||
keyboard = self.create_tic_tac_toe_keyboard(board)
|
keyboard = self.create_tic_tac_toe_keyboard(board)
|
||||||
@@ -546,6 +560,167 @@ ID: {user_info['ID']}
|
|||||||
context.user_data.pop('quiz_correct', None)
|
context.user_data.pop('quiz_correct', None)
|
||||||
context.user_data.pop('quiz_question', None)
|
context.user_data.pop('quiz_question', None)
|
||||||
|
|
||||||
|
async def start_battleship_game(self, query, context):
|
||||||
|
"""Запуск игры 'Морской бой'"""
|
||||||
|
# Создаем поле 5x5 для упрощения (вместо 10x10)
|
||||||
|
board = [['~' for _ in range(5)] for _ in range(5)]
|
||||||
|
bot_ships = self.place_ships() # Размещаем корабли бота
|
||||||
|
|
||||||
|
# Сохраняем состояние игры
|
||||||
|
context.user_data['battleship_board'] = board
|
||||||
|
context.user_data['battleship_bot_ships'] = bot_ships
|
||||||
|
context.user_data['battleship_shots'] = 0
|
||||||
|
context.user_data['battleship_hits'] = 0
|
||||||
|
|
||||||
|
# Создаем клавиатуру для стрельбы
|
||||||
|
keyboard = self.create_battleship_keyboard(board)
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
await query.edit_message_text(GAME_MESSAGES['battleship_start'], reply_markup=reply_markup, parse_mode='HTML')
|
||||||
|
|
||||||
|
def create_battleship_keyboard(self, board):
|
||||||
|
"""Создание клавиатуры для Морского боя"""
|
||||||
|
keyboard = []
|
||||||
|
|
||||||
|
# Заголовки столбцов (0-4)
|
||||||
|
header_row = [InlineKeyboardButton(" ", callback_data='bs_header')]
|
||||||
|
for j in range(5):
|
||||||
|
header_row.append(InlineKeyboardButton(str(j), callback_data='bs_header'))
|
||||||
|
keyboard.append(header_row)
|
||||||
|
|
||||||
|
# Строки с буквами и клетками
|
||||||
|
letters = ['A', 'B', 'C', 'D', 'E']
|
||||||
|
for i in range(5):
|
||||||
|
row = [InlineKeyboardButton(letters[i], callback_data='bs_header')]
|
||||||
|
for j in range(5):
|
||||||
|
cell = board[i][j]
|
||||||
|
callback_data = f'bs_{i}{j}' # Координаты в формате row,col
|
||||||
|
row.append(InlineKeyboardButton(cell, callback_data=callback_data))
|
||||||
|
keyboard.append(row)
|
||||||
|
|
||||||
|
# Кнопка назад
|
||||||
|
keyboard.append([InlineKeyboardButton("⬅️ Назад к играм", callback_data='cmd_play_game')])
|
||||||
|
|
||||||
|
return keyboard
|
||||||
|
|
||||||
|
def place_ships(self):
|
||||||
|
"""Размещение кораблей бота (упрощенная версия - 3 корабля)"""
|
||||||
|
ships = [] # Список кораблей как список координат
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Размещаем 3 корабля: 3-клеточный, 2-клеточный, 1-клеточный
|
||||||
|
ship_sizes = [3, 2, 1]
|
||||||
|
|
||||||
|
for size in ship_sizes:
|
||||||
|
placed = False
|
||||||
|
while not placed:
|
||||||
|
# Случайное направление: 0 - горизонтально, 1 - вертикально
|
||||||
|
direction = random.randint(0, 1)
|
||||||
|
if direction == 0: # Горизонтально
|
||||||
|
row = random.randint(0, 4)
|
||||||
|
col = random.randint(0, 4 - size)
|
||||||
|
ship_coords = [(row, col + i) for i in range(size)]
|
||||||
|
else: # Вертикально
|
||||||
|
row = random.randint(0, 4 - size)
|
||||||
|
col = random.randint(0, 4)
|
||||||
|
ship_coords = [(row + i, col) for i in range(size)]
|
||||||
|
|
||||||
|
# Проверяем, что корабль не пересекает другие
|
||||||
|
if not any(coord in [c for ship in ships for c in ship] for coord in ship_coords):
|
||||||
|
ships.append(ship_coords)
|
||||||
|
placed = True
|
||||||
|
|
||||||
|
return ships
|
||||||
|
|
||||||
|
async def handle_battleship_shot(self, query, context):
|
||||||
|
"""Обработка выстрела в Морском бое"""
|
||||||
|
if query.data == 'bs_header':
|
||||||
|
return # Игнорируем клики по заголовкам
|
||||||
|
|
||||||
|
# Парсим координаты
|
||||||
|
coords = query.data[3:] # bs_01 -> 01
|
||||||
|
if len(coords) != 2:
|
||||||
|
await query.answer(GAME_MESSAGES['battleship_invalid_coord'])
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
row, col = int(coords[0]), int(coords[1])
|
||||||
|
except ValueError:
|
||||||
|
await query.answer(GAME_MESSAGES['battleship_invalid_coord'])
|
||||||
|
return
|
||||||
|
|
||||||
|
board = context.user_data.get('battleship_board')
|
||||||
|
bot_ships = context.user_data.get('battleship_bot_ships')
|
||||||
|
|
||||||
|
if not board or not (0 <= row < 5 and 0 <= col < 5):
|
||||||
|
await query.answer(GAME_MESSAGES['battleship_invalid_coord'])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Проверяем, стреляли ли уже в эту клетку
|
||||||
|
if board[row][col] != '~':
|
||||||
|
await query.answer(GAME_MESSAGES['battleship_already_shot'])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Обновляем счетчик выстрелов
|
||||||
|
context.user_data['battleship_shots'] += 1
|
||||||
|
|
||||||
|
# Проверяем попадание
|
||||||
|
hit = False
|
||||||
|
ship_hit = None
|
||||||
|
for ship in bot_ships:
|
||||||
|
if (row, col) in ship:
|
||||||
|
hit = True
|
||||||
|
ship_hit = ship
|
||||||
|
break
|
||||||
|
|
||||||
|
if hit:
|
||||||
|
board[row][col] = '💥' # Попадание
|
||||||
|
context.user_data['battleship_hits'] += 1
|
||||||
|
|
||||||
|
# Проверяем, потоплен ли корабль
|
||||||
|
ship_sunk = all(board[r][c] == '💥' for r, c in ship_hit)
|
||||||
|
if ship_sunk:
|
||||||
|
# Помечаем потопленный корабль
|
||||||
|
for r, c in ship_hit:
|
||||||
|
board[r][c] = '🔥'
|
||||||
|
await query.answer(GAME_MESSAGES['battleship_sunk'])
|
||||||
|
else:
|
||||||
|
await query.answer(GAME_MESSAGES['battleship_hit'])
|
||||||
|
else:
|
||||||
|
board[row][col] = '💧' # Промах
|
||||||
|
await query.answer(GAME_MESSAGES['battleship_miss'])
|
||||||
|
|
||||||
|
# Проверяем победу (все корабли потоплены)
|
||||||
|
total_ship_cells = sum(len(ship) for ship in bot_ships)
|
||||||
|
if context.user_data['battleship_hits'] >= total_ship_cells:
|
||||||
|
# Победа игрока
|
||||||
|
db.update_score(query.from_user.id, SCORE_VALUES['tic_tac_toe_win']) # +15 очков
|
||||||
|
db.update_reputation(query.from_user.id, SCORE_VALUES['reputation_per_message'])
|
||||||
|
|
||||||
|
# Проверяем повышение ранга
|
||||||
|
rank_update = db.update_rank(query.from_user.id, query.message.chat.id, query.from_user.first_name)
|
||||||
|
if rank_update and rank_update.get("promoted"):
|
||||||
|
await query.message.chat.send_message(
|
||||||
|
RANK_MESSAGES['promotion_message'].format(
|
||||||
|
name=rank_update['name'],
|
||||||
|
new_rank=rank_update['new_rank']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard = self.create_battleship_keyboard(board)
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text(GAME_MESSAGES['battleship_win'], reply_markup=reply_markup)
|
||||||
|
# Очистка данных игры
|
||||||
|
context.user_data.pop('battleship_board', None)
|
||||||
|
context.user_data.pop('battleship_bot_ships', None)
|
||||||
|
context.user_data.pop('battleship_shots', None)
|
||||||
|
context.user_data.pop('battleship_hits', None)
|
||||||
|
else:
|
||||||
|
# Продолжаем игру - обновляем клавиатуру
|
||||||
|
keyboard = self.create_battleship_keyboard(board)
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text(GAME_MESSAGES['battleship_start'], reply_markup=reply_markup, parse_mode='HTML')
|
||||||
|
|
||||||
async def ban_user(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def ban_user(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Забанить пользователя (только админы)"""
|
"""Забанить пользователя (только админы)"""
|
||||||
if not await self.is_admin(update.effective_chat, update.effective_user.id):
|
if not await self.is_admin(update.effective_chat, update.effective_user.id):
|
||||||
@@ -568,11 +743,11 @@ ID: {user_info['ID']}
|
|||||||
async def unban_user(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def unban_user(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Разбанить пользователя (только админы)"""
|
"""Разбанить пользователя (только админы)"""
|
||||||
if not await self.is_admin(update.effective_chat, update.effective_user.id):
|
if not await self.is_admin(update.effective_chat, update.effective_user.id):
|
||||||
await update.message.reply_text("❌ У вас нет прав для выполнения этой команды.")
|
await update.message.reply_text(MODERATION_MESSAGES['no_permission'])
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(context.args) < 1:
|
if len(context.args) < 1:
|
||||||
await update.message.reply_text("Использование: /unban [пользователь]")
|
await update.message.reply_text(MODERATION_MESSAGES['unban_usage'])
|
||||||
return
|
return
|
||||||
|
|
||||||
user_id = context.args[0]
|
user_id = context.args[0]
|
||||||
@@ -586,11 +761,11 @@ ID: {user_info['ID']}
|
|||||||
async def mute_user(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def mute_user(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Заглушить пользователя (только админы)"""
|
"""Заглушить пользователя (только админы)"""
|
||||||
if not await self.is_admin(update.effective_chat, update.effective_user.id):
|
if not await self.is_admin(update.effective_chat, update.effective_user.id):
|
||||||
await update.message.reply_text("❌ У вас нет прав для выполнения этой команды.")
|
await update.message.reply_text(MODERATION_MESSAGES['no_permission'])
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(context.args) < 2:
|
if len(context.args) < 2:
|
||||||
await update.message.reply_text("Использование: /mute [пользователь] [время_в_секундах]")
|
await update.message.reply_text(MODERATION_MESSAGES['mute_usage'])
|
||||||
return
|
return
|
||||||
|
|
||||||
user_id = context.args[0]
|
user_id = context.args[0]
|
||||||
@@ -1089,6 +1264,7 @@ ID: {user_info['ID']}
|
|||||||
[InlineKeyboardButton("Камень-ножницы-бумага", callback_data='game_rps')],
|
[InlineKeyboardButton("Камень-ножницы-бумага", callback_data='game_rps')],
|
||||||
[InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')],
|
[InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')],
|
||||||
[InlineKeyboardButton("Викторина", callback_data='game_quiz')],
|
[InlineKeyboardButton("Викторина", callback_data='game_quiz')],
|
||||||
|
[InlineKeyboardButton("Морской бой", callback_data='game_battleship')],
|
||||||
[InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')]
|
[InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')]
|
||||||
]
|
]
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|||||||
@@ -177,6 +177,14 @@ GAME_MESSAGES = {
|
|||||||
'quiz_not_found': 'Вопрос викторины не найден.',
|
'quiz_not_found': 'Вопрос викторины не найден.',
|
||||||
'invalid_move': 'Неверный ход!',
|
'invalid_move': 'Неверный ход!',
|
||||||
'game_over': 'Неверный формат хода!',
|
'game_over': 'Неверный формат хода!',
|
||||||
|
'battleship_start': '🚢 <b>Морской бой!</b>\n\nБот разместил корабли. Ваша очередь стрелять!\n\nПоле 5x5 для удобства.\nИспользуйте кнопки ниже для стрельбы.',
|
||||||
|
'battleship_hit': '💥 Попадание!',
|
||||||
|
'battleship_miss': '💧 Промах!',
|
||||||
|
'battleship_sunk': '💥 Корабль потоплен!',
|
||||||
|
'battleship_win': '🎉 Поздравляем! Вы победили в Морском бое! +15 очков',
|
||||||
|
'battleship_lose': '😞 Вы проиграли! Все корабли потоплены.',
|
||||||
|
'battleship_already_shot': 'Вы уже стреляли в эту клетку!',
|
||||||
|
'battleship_invalid_coord': 'Неверные координаты!',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Сообщения погоды
|
# Сообщения погоды
|
||||||
|
|||||||
Reference in New Issue
Block a user