Files
2025-07-10 21:02:34 +03:00

374 lines
16 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#
# @@@@@@ @@@@@@ @@@@@@@ @@@@@@@ @@@@@@ @@@@@@@@@@ @@@@@@ @@@@@@@ @@@ @@@ @@@ @@@@@@@@ @@@@@@
# @@@@@@@@ @@@@@@@ @@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@ @@@ @@@ @@@@@@@@ @@@@@@@
# @@! @@@ !@@ @@! @@! @@@ @@! @@@ @@! @@! @@! @@! @@@ @@! @@@ @@! @@@ @@! @@! !@@
# !@! @!@ !@! !@! !@! @!@ !@! @!@ !@! !@! !@! !@! @!@ !@! @!@ !@! @!@ !@! !@! !@!
# @!@!@!@! !!@@!! @!! @!@!!@! @!@ !@! @!! !!@ @!@ @!@ !@! @!@ !@! @!@ !@! @!! @!!!:! !!@@!!
# !!!@!!!! !!@!!! !!! !!@!@! !@! !!! !@! ! !@! !@! !!! !@! !!! !@! !!! !!! !!!!!: !!@!!!
# !!: !!! !:! !!: !!: :!! !!: !!! !!: !!: !!: !!! !!: !!! !!: !!! !!: !!: !:!
# :!: !:! !:! :!: :!: !:! :!: !:! :!: :!: :!: !:! :!: !:! :!: !:! :!: :!: !:!
# :: ::: :::: :: :: :: ::: ::::: :: ::: :: ::::: :: :::: :: ::::: :: :: :::: :: :::: :::: ::
# : : : :: : : : : : : : : : : : : : : :: : : : : : : :: : : : :: :: :: : :
#
# © Copyright 2024
#
# https://t.me/Den4ikSuperOstryyPer4ik
# and
# https://t.me/ToXicUse
#
# 🔒 Licensed under the GNU AGPLv3
# https://www.gnu.org/licenses/agpl-3.0.html
#
# meta banner: https://raw.githubusercontent.com/Den4ikSuperOstryyPer4ik/Astro-modules/main/Banners/MineSwepper.png
# meta developer: @AstroModules
import random
from .. import loader, utils
class Cell:
"""Cell on the minesweeper board"""
def __init__(self, value: int = 0, is_mine: bool = False, is_visible: bool = False):
self.is_mine = is_mine
self.value = value
self.is_visible = is_visible
self.emoji = "◽️"
self.emoji2 = ""
class MineSweeperGame:
"""Minesweeper game"""
def __init__(self, rows: int, cols: int, mines: int):
self.mode = 1 # 1 - miner, 2 - flag
self.flags = 0
self.board: list[list[Cell]] = []
self.rows = rows
self.cols = cols
self.mines = mines
def generate_board(self):
"""Generate the minesweeper board"""
self.board = [
[
Cell()
for _ in range(self.cols)
] for _ in range(self.rows)
]
mine_positions = random.sample(range(self.rows * self.cols), self.mines)
for position in mine_positions:
row = position // self.cols
col = position % self.cols
self.board[row][col].value = None
self.board[row][col].is_mine = True
self.board[row][col].emoji2 = "💣"
for i in range(row - 1, row + 2):
for j in range(col - 1, col + 2):
if 0 <= i < self.rows and 0 <= j < self.cols and not self.board[i][j].is_mine:
self.board[i][j].value += 1
for row in self.board:
for cell in row:
if cell.value:
cell.emoji2 = {
1: "1",
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
}[cell.value]
def reveal_cell(self, row_index, col):
"""Reveal a cell on the minesweeper board"""
if self.board[row_index][col].is_mine:
if len([
cell
for row in self.board
for cell in row
if cell.is_visible
]) in [0, 1]:
self.generate_board()
return self.reveal_cell(row_index, col)
return False
elif self.board[row_index][col].value == 0:
self._reveal_empty_cells(row_index, col)
else:
self.board[row_index][col].is_visible = True
return self.board
def reveal_next_to_empty_cells(self):
"""Reveal all next to empty cells"""
for index_row, row in enumerate(self.board):
for index_cell, cell in enumerate(row):
if cell.value == 0 and cell.is_visible:
for i in range(index_row - 1, index_row + 2):
for j in range(index_cell - 1, index_cell + 2):
if 0 <= i < self.rows and 0 <= j < self.cols:
if not self.board[i][j].is_mine and not self.board[i][j].is_visible:
self.reveal_cell(i, j)
def _reveal_empty_cells(self, row, col):
"""Reveal all empty cells connected to the given cell"""
if self.board[row][col].value == 0:
self.board[row][col].is_visible = True
for i in range(row - 1, row + 2):
for j in range(col - 1, col + 2):
if 0 <= i < self.rows and 0 <= j < self.cols:
if not self.board[i][j].is_mine and not self.board[i][j].is_visible and self.board[i][j].value == 0:
self.reveal_cell(i, j)
class MineSwepperModule(loader.Module):
"""Minesweeper game"""
strings = {
"name": "MineSweeper",
"mines": "Number of mines",
"rows": "Number of rows",
"cols": "Number of columns",
"game": "🎮 <b>MineSweeper game</b>\n\n💣 <b>{mines}</b> mines\n🧮 <b>{rows}</b> rows\n💈 <b>{cols}</b> columns\n🚩 <b>{flags}</b> flags",
"game-over": "❌ <b>Game over!</b>\n💣 <b>You hit a mine.</b>\n\n💣 <b>{mines}</b> mines\n🧮 <b>{rows}</b> rows\n💈 <b>{cols}</b> columns\n🚩 <b>{flags}</b> flags",
"game-end": "❌ <b>Game over!</b>\n🎉 <b>You win!</b>\n\n💣 <b>{mines}</b> mines\n🧮 <b>{rows}</b> rows\n💈 <b>{cols}</b> columns\n🚩 <b>{flags}</b> flags",
"game-ended": "❌ Game over!",
"continue-or-start-new-game": "⚠️ <b>Game already started!</b>\n❔ Continue or start new game?",
"continue": "▶️ Continue",
"start-new-game": "🆕 Start new game",
"game-title": "🎮 <b>MineSweeper game</b>",
"game-not-found": "❌ Game not found!",
"cell-flagged": "🚩 Cell flagged",
"switch-mode": "🔄 Switch to «🚩» mode",
}
strings_ru = {
"_cls_doc": "Игра \"Сапёр\"",
"mines": "Количество мин",
"rows": "Количество строк",
"cols": "Количество столбцов",
"game": "🎮 <b>Игра \"Сапёр\"</b>\n\n💣 <b>{mines}</b> мин\n🧮 <b>{rows}</b> строк\n💈 <b>{cols}</b> столбцов\n🚩 <b>{flags}</b> флагов",
"game-over": "❌ <b>Игра окончена!</b>\n<b>💣 Вы попали на мину.</b>\n\n💣 <b>{mines}</b> мин\n🧮 <b>{rows}</b> строк\n💈 <b>{cols}</b> столбцов\n🚩 <b>{flags}</b> флагов",
"game-end": "❌ <b>Игра окончена!</b> \n<b>🎉 Вы выиграли!</b>\n\n💣 <b>{mines}</b> мин\n🧮 <b>{rows}</b> строк\n💈 <b>{cols}</b> столбцов\n🚩 <b>{flags}</b> флагов",
"game-ended": "❌ Игра окончена!",
"continue-or-start-new-game": "⚠️ <b>В этом чате уже идёт игра!</b>\n❔ Продолжить или начать новую игру?",
"continue": "▶️ Продолжить",
"start-new-game": "🆕 Начать новую игру",
"game-title": "🎮 <b>Игра \"Сапёр\"</b>",
"game-not-found": "❌ Игра не найдена! Возможно она уже завершена.",
"cell-flagged": "🚩 Поле помечено",
"switch-mode": "🔄 Переключиться на режим «{}»"
}
def __init__(self):
self.games = {}
self.config = loader.ModuleConfig(
loader.ConfigValue(
"mines",
10,
lambda: self.strings("mines"),
validator=loader.validators.Integer(minimum=1, maximum=99),
),
loader.ConfigValue(
"rows",
8,
lambda: self.strings("rows"),
validator=loader.validators.Integer(minimum=1, maximum=10),
),
loader.ConfigValue(
"cols",
8,
lambda: self.strings("cols"),
validator=loader.validators.Integer(minimum=1, maximum=8),
)
)
def generate_markup(self, chat_id):
if not self.games.get(chat_id):
return
return [
[
{
"text": i.emoji2 if i.is_visible else i.emoji,
"callback": self.mine if self.games.get(chat_id).mode == 1 else self.flag,
"kwargs": {
"row": self.games.get(chat_id).board.index(row),
"col": row.index(i),
"chat_id": chat_id
}
}
for i in row
]
for row in self.games.get(chat_id).board
] + [
[
{
"text": self.strings("switch-mode").format('🚩' if self.games.get(chat_id).mode == 1 else '⛏️'),
"callback": self.switch_mode,
"kwargs": {
"chat_id": chat_id
}
}
]
]
async def switch_mode(self, call, chat_id):
self.games.get(chat_id).mode = 1 if self.games.get(chat_id).mode == 2 else 2
await call.edit(
self.strings("game").format(
mines=self.games.get(chat_id).mines,
rows=self.games.get(chat_id).rows,
cols=self.games.get(chat_id).cols,
flags=self.games.get(chat_id).flags
),
reply_markup=self.generate_markup(chat_id)
)
async def flag(self, call, row, col, chat_id):
if self.games.get(chat_id).mode != 2:
return
if self.games.get(chat_id).board[row][col].emoji != "🚩":
self.games.get(chat_id).flags += 1
self.games.get(chat_id).board[row][col].emoji = "🚩"
else:
self.games.get(chat_id).flags -= 1
self.games.get(chat_id).board[row][col].emoji = "◽️"
await call.edit(
self.strings("game").format(
mines=self.games.get(chat_id).mines,
rows=self.games.get(chat_id).rows,
cols=self.games.get(chat_id).cols,
flags=self.games.get(chat_id).flags
),
reply_markup=self.generate_markup(chat_id)
)
@loader.command(
ru_doc="- начать игру \"Сапёр\"",
alias="mines",
)
async def minesweeper(self, message):
"""- start the game "Minesweeper" """
chat_id: int = utils.get_chat_id(message)
if chat_id in self.games:
await utils.answer(message, self.strings("continue-or-start-new-game"), reply_markup=[
[
{
"text": self.strings("continue"),
"callback": self.continue_game,
"kwargs": {
"chat_id": chat_id
}
}
],
[
{
"text": self.strings("start-new-game"),
"callback": self.start_game,
"kwargs": {
"chat_id": chat_id
}
}
]
])
else:
await utils.answer(message, self.strings("game-title"), reply_markup=[
[
{
"text": self.strings("start-new-game"),
"callback": self.start_game,
"kwargs": {
"chat_id": chat_id
}
}
]
])
async def start_game(self, call, chat_id: int):
self.games[chat_id] = MineSweeperGame(
rows=self.config["rows"],
cols=self.config["cols"],
mines=self.config["mines"],
)
self.games.get(chat_id).generate_board()
await call.edit(
self.strings("game").format(
mines=self.games.get(chat_id).mines,
rows=self.games.get(chat_id).rows,
cols=self.games.get(chat_id).cols,
flags=self.games.get(chat_id).flags
),
reply_markup=self.generate_markup(chat_id)
)
async def continue_game(self, call, chat_id: int):
await call.edit(
self.strings("game").format(
mines=self.games.get(chat_id).mines,
rows=self.games.get(chat_id).rows,
cols=self.games.get(chat_id).cols,
flags=self.games.get(chat_id).flags
),
reply_markup=self.generate_markup(chat_id)
)
async def mine(self, call, row, col, chat_id):
if not self.games.get(chat_id, None):
return await call.answer(self.strings("game-not-found"), show_alert=True)
if self.games.get(chat_id).board[row][col].emoji == "🚩":
return await call.answer(self.strings("cell-flagged"), show_alert=True)
result = self.games.get(chat_id).reveal_cell(row, col)
self.games.get(chat_id).reveal_next_to_empty_cells()
if not result or len([
i
for row in self.games.get(chat_id).board
for i in row
if not i.is_visible
]) == self.games.get(chat_id).mines:
await call.edit(
self.strings("game-over" if not result else "game-end").format(
mines=self.games.get(chat_id).mines,
rows=self.games.get(chat_id).rows,
cols=self.games.get(chat_id).cols,
flags=self.games.get(chat_id).flags
),
reply_markup=[
[
{
"text": i.emoji2,
"action": "answer",
"message": self.strings("game-ended")
}
for i in row
]
for row in self.games.get(chat_id).board
] + [
[
{
"text": self.strings("start-new-game"),
"callback": self.start_game,
"kwargs": {
"chat_id": chat_id
}
}
]
])
del self.games[chat_id]
else:
await call.edit(
self.strings("game").format(
mines=self.games.get(chat_id).mines,
rows=self.games.get(chat_id).rows,
cols=self.games.get(chat_id).cols,
flags=self.games.get(chat_id).flags
),
reply_markup=self.generate_markup(chat_id)
)