#
# @@@@@@ @@@@@@ @@@@@@@ @@@@@@@ @@@@@@ @@@@@@@@@@ @@@@@@ @@@@@@@ @@@ @@@ @@@ @@@@@@@@ @@@@@@
# @@@@@@@@ @@@@@@@ @@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@ @@@ @@@ @@@@@@@@ @@@@@@@
# @@! @@@ !@@ @@! @@! @@@ @@! @@@ @@! @@! @@! @@! @@@ @@! @@@ @@! @@@ @@! @@! !@@
# !@! @!@ !@! !@! !@! @!@ !@! @!@ !@! !@! !@! !@! @!@ !@! @!@ !@! @!@ !@! !@! !@!
# @!@!@!@! !!@@!! @!! @!@!!@! @!@ !@! @!! !!@ @!@ @!@ !@! @!@ !@! @!@ !@! @!! @!!!:! !!@@!!
# !!!@!!!! !!@!!! !!! !!@!@! !@! !!! !@! ! !@! !@! !!! !@! !!! !@! !!! !!! !!!!!: !!@!!!
# !!: !!! !:! !!: !!: :!! !!: !!! !!: !!: !!: !!! !!: !!! !!: !!! !!: !!: !:!
# :!: !:! !:! :!: :!: !:! :!: !:! :!: :!: :!: !:! :!: !:! :!: !:! :!: :!: !:!
# :: ::: :::: :: :: :: ::: ::::: :: ::: :: ::::: :: :::: :: ::::: :: :: :::: :: :::: :::: ::
# : : : :: : : : : : : : : : : : : : : :: : : : : : : :: : : : :: :: :: : :
#
# © 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": "🎮 MineSweeper game\n\n💣 {mines} mines\n🧮 {rows} rows\n💈 {cols} columns\n🚩 {flags} flags",
"game-over": "❌ Game over!\n💣 You hit a mine.\n\n💣 {mines} mines\n🧮 {rows} rows\n💈 {cols} columns\n🚩 {flags} flags",
"game-end": "❌ Game over!\n🎉 You win!\n\n💣 {mines} mines\n🧮 {rows} rows\n💈 {cols} columns\n🚩 {flags} flags",
"game-ended": "❌ Game over!",
"continue-or-start-new-game": "⚠️ Game already started!\n❔ Continue or start new game?",
"continue": "▶️ Continue",
"start-new-game": "🆕 Start new game",
"game-title": "🎮 MineSweeper game",
"game-not-found": "❌ Game not found!",
"cell-flagged": "🚩 Cell flagged",
"switch-mode": "🔄 Switch to «🚩» mode",
}
strings_ru = {
"_cls_doc": "Игра \"Сапёр\"",
"mines": "Количество мин",
"rows": "Количество строк",
"cols": "Количество столбцов",
"game": "🎮 Игра \"Сапёр\"\n\n💣 {mines} мин\n🧮 {rows} строк\n💈 {cols} столбцов\n🚩 {flags} флагов",
"game-over": "❌ Игра окончена!\n💣 Вы попали на мину.\n\n💣 {mines} мин\n🧮 {rows} строк\n💈 {cols} столбцов\n🚩 {flags} флагов",
"game-end": "❌ Игра окончена! \n🎉 Вы выиграли!\n\n💣 {mines} мин\n🧮 {rows} строк\n💈 {cols} столбцов\n🚩 {flags} флагов",
"game-ended": "❌ Игра окончена!",
"continue-or-start-new-game": "⚠️ В этом чате уже идёт игра!\n❔ Продолжить или начать новую игру?",
"continue": "▶️ Продолжить",
"start-new-game": "🆕 Начать новую игру",
"game-title": "🎮 Игра \"Сапёр\"",
"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)
)