From cfce663f891dee65133cc370b4ba001186ade0db Mon Sep 17 00:00:00 2001 From: Groosha Date: Sat, 4 Sep 2021 21:54:28 +0300 Subject: [PATCH] The game field is generated only after first click Also switching to flags is not disallowed until you make first click --- bot/handlers/callbacks.py | 17 +++++++++++++++-- bot/minesweeper/game.py | 20 ++++++++++++++------ bot/minesweeper/generators.py | 6 ++++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/bot/handlers/callbacks.py b/bot/handlers/callbacks.py index d43dcdb..bb6d395 100644 --- a/bot/handlers/callbacks.py +++ b/bot/handlers/callbacks.py @@ -9,7 +9,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.exc import IntegrityError -from bot.minesweeper.game import get_newgame_data, untouched_cells_count, all_flags_match_bombs, make_text_table +from bot.minesweeper.game import (get_fake_newgame_data, untouched_cells_count, all_flags_match_bombs, + make_text_table, get_real_game_data) from bot.minesweeper.states import ClickMode, CellMask from bot.keyboards.kb_minefield import make_keyboard_from_minefield from bot.cbdata import cb_newgame, cb_click, cb_switch_mode, cb_switch_flag, cb_ignore @@ -69,7 +70,7 @@ async def callback_newgame(call: types.CallbackQuery, state: FSMContext, callbac bombs = int(callback_data.get("bombs")) game_id = str(uuid4()) - newgame_dict = {"game_id": game_id, "game_data": get_newgame_data(size, bombs)} + newgame_dict = {"game_id": game_id, "game_data": get_fake_newgame_data(size, bombs)} await state.set_data(newgame_dict) await call.message.edit_text( f"You're currently playing {size}×{size} field, {bombs} bombs", @@ -90,6 +91,14 @@ async def callback_open_square(call: types.CallbackQuery, state: FSMContext, x = int(callback_data["x"]) y = int(callback_data["y"]) + # If this is the first click, it's time to generate the real game field + if game_data["initial"] is True: + cells = get_real_game_data(game_data["size"], game_data["bombs"], (x, y)) + game_data["cells"] = cells + game_data["initial"] = False + else: + cells = game_data.get("cells") + # This cell contained a bomb if cells[x][y]["value"] == "*": cells[x][y]["mask"] = CellMask.BOMB @@ -143,6 +152,10 @@ async def switch_click_mode(call: types.CallbackQuery, state: FSMContext, callba game_data = fsm_data.get("game_data", {}) cells = game_data.get("cells") + if game_data["initial"] is True: + await call.answer(show_alert=True, text="You can only place flags after first click!") + return + game_data["current_mode"] = int(callback_data["new_mode"]) await state.update_data(game_data=game_data) diff --git a/bot/minesweeper/game.py b/bot/minesweeper/game.py index 870fdd2..38c86d3 100644 --- a/bot/minesweeper/game.py +++ b/bot/minesweeper/game.py @@ -1,12 +1,12 @@ -from typing import Dict, List +from typing import Dict, List, Tuple from texttable import Texttable -from bot.minesweeper.generators import generate_custom +from bot.minesweeper.generators import generate_custom, generate_square_field from bot.minesweeper.states import CellMask, ClickMode -def get_newgame_data(size: int, bombs: int) -> Dict: +def get_fake_newgame_data(size: int, bombs: int) -> Dict: """ Prepares a new game dictionary @@ -14,15 +14,23 @@ def get_newgame_data(size: int, bombs: int) -> Dict: :param bombs: number of bombs to place :return: a dictionary with field data for a new game """ - result = {"current_mode": ClickMode.CLICK, "size": size, "bombs": bombs} - field = generate_custom(size, bombs) + result = {"current_mode": ClickMode.CLICK, "size": size, "bombs": bombs, "initial": True} + field = generate_square_field(size) for x in range(size): for y in range(size): - field[x][y] = {"value": field[x][y], "mask": 0, "x": x, "y": y} + field[x][y] = {"value": field[x][y], "mask": CellMask.HIDDEN, "x": x, "y": y} result["cells"] = field return result +def get_real_game_data(size: int, bombs: int, predefined: Tuple[int, int]) -> List[List[Dict]]: + field = generate_custom(size, bombs, predefined) + for x in range(size): + for y in range(size): + field[x][y] = {"value": field[x][y], "mask": CellMask.HIDDEN, "x": x, "y": y} + return field + + def untouched_cells_count(cells: List[List[Dict]]) -> int: """ Counts the number of "untouched" cells: those which status is HIDDEN diff --git a/bot/minesweeper/generators.py b/bot/minesweeper/generators.py index 2e4d686..f35db6a 100644 --- a/bot/minesweeper/generators.py +++ b/bot/minesweeper/generators.py @@ -65,7 +65,7 @@ def __find_neighbours(x: int, y: int, size: int) -> List[Tuple]: return result -def generate_custom(size: int, bombs: int) -> List[List]: +def generate_custom(size: int, bombs: int, predefined: Tuple[int, int]) -> List[List]: """ Generates custom square field with bombs (*). If cell contains a bomb, it has "*" value. @@ -73,6 +73,7 @@ def generate_custom(size: int, bombs: int) -> List[List]: :param size: a single dimension of a field :param bombs: bombs count for this field + :param predefined: coordinates of cell, which MUST be free of bombs :return: an array of arrays of cells. """ field = generate_square_field(size) @@ -82,7 +83,8 @@ def generate_custom(size: int, bombs: int) -> List[List]: x = randint(0, size-1) y = randint(0, size-1) - if field[x][y] == "*": + # Do not place a bomb on another bomb or on predefined place + if (x == predefined[0] and y == predefined[1]) or field[x][y] == "*": continue field[x][y] = "*" neighbours = __find_neighbours(x, y, size)