Skip to content

R0903:too-few-public-methods for private methods #10586

@incendius679

Description

@incendius679

Pylint is giving me an error saying that two of my private classes don't have enough public methods. These classes are used for a very complex primary class, and these methods are there for a single primary method for each of these secondary classes. I could have put everything in a single function, but it was just unreadable and huge. I was wondering if this was intentional or if it was a bug, because I don't think it makes much sense to include this error for private classes. In any case, here is the program:

(PS: The error triger the class _MineManager and the class _RevealManager)

"""Manage the grid of cells in a Minesweeper game.

This module defines the `Grid` class, which manages the cells of the Minesweeper game,
including mine placement and revealing cells. It also includes managers for mine
placement and cell revealing.
"""

import secrets
from typing import Self

import pygame

from cell import Cell


class _MineManager:  # The first error is here
    """Manager for mine placement and counting around cells.

    This class handles the placement of mines on the grid, ensuring that a specified
    cell is safe (does not contain a mine) and that the number of mines placed matches
    the specified count. It also counts the number of mines around each cell after
    mines are placed.

    Attributes:
        _grid (Grid): The grid containing the cells.
        _cells (list[list[Cell]]): A 2D list of Cell objects representing the grid.
    """

    def __init__(self: Self, grid: "Grid") -> None:
        self._grid: Grid = grid
        self._cells: list[list[Cell]] = grid.cells

    def place_mines(self: Self, safe_x: int, safe_y: int) -> None:
        """Place mines on the grid, ensuring that the cell at (safe_x, safe_y) is safe.

        This method places mines randomly on the grid, avoiding the cell at (safe_x,
        safe_y) and its adjacent cells. It ensures that the number of mines placed
        equals the specified mines_count in the grid.

        Args:
            safe_x (int): The x-coordinate of the cell that should not contain a mine.
            safe_y (int): The y-coordinate of the cell that should not contain a mine.
        """
        forbidden: set[tuple[int, int]] = {
            (pos_x, pos_y)
            for pos_x in range(safe_x - 1, safe_x + 2)
            for pos_y in range(safe_y - 1, safe_y + 2)
            if 0 <= pos_x < self._grid.cell_num and 0 <= pos_y < self._grid.cell_num
        }
        candidates: list[tuple[int, int]] = [
            (pos_x, pos_y)
            for pos_x in range(self._grid.cell_num)
            for pos_y in range(self._grid.cell_num)
            if (pos_x, pos_y) not in forbidden
        ]
        cell: Cell
        for pos_x, pos_y in secrets.SystemRandom().sample(
            candidates, self._grid.mines_count
        ):
            cell = self._cells[pos_x][pos_y]
            cell.cellstate.is_mine = True
        for pos_x in range(self._grid.cell_num):
            for pos_y in range(self._grid.cell_num):
                cell = self._cells[pos_x][pos_y]
                cell.cellstate.mines_around = self._count_mines_around(pos_x, pos_y)

    def _count_mines_around(self: Self, pos_x: int, pos_y: int) -> int:
        """Count the number of mines around a given cell.

        This method counts the number of mines in the adjacent cells of the cell at
        (pos_x, pos_y). It checks all eight surrounding cells and returns the count.

        Args:
            pos_x (int): The x-coordinate of the cell to check.
            pos_y (int): The y-coordinate of the cell to check.

        Returns:
            int: The number of mines around the cell at (pos_x, pos_y).
        """
        cells: list[list[Cell]] = self._cells
        return sum(
            cells[nx][ny].cellstate.is_mine
            for dx in range(-1, 2)
            for dy in range(-1, 2)
            if 0 <= (nx := pos_x + dx) < self._grid.cell_num
            and 0 <= (ny := pos_y + dy) < self._grid.cell_num
        )


class _RevealManager:  # The second error is here.
    """Manager for revealing cells in the Minesweeper game.

    This class handles the logic for revealing cells when a cell is clicked. It manages
    the game state, including mine placement and checking win conditions.

    Attributes:
        _grid (Grid): The grid containing the cells.
        _cells (list[list[Cell]]): A 2D list of Cell objects representing the grid.
        _mines_placed (bool): Flag indicating if mines have been placed on the grid.
    """

    def __init__(self: Self, grid: "Grid") -> None:
        self._grid: Grid = grid
        self._cells: list[list[Cell]] = grid.cells
        self._mines_placed: bool = False

    def reveal_cell(self: Self, pos_x: int, pos_y: int) -> bool:
        """Reveal a cell at the specified position.

        This method reveals the cell at (pos_x, pos_y). If the cell is a mine, it
        returns True, indicating a mine was hit. If the cell is not a mine and has no
        adjacent mines, it reveals all adjacent.

        Args:
            pos_x (int): x-coordinate of the cell to reveal.
            pos_y (int): y-coordinate of the cell to reveal.

        Returns:
            bool: True if a mine was hit, False otherwise.
        """
        cell: Cell = self._cells[pos_x][pos_y]
        if not self._mines_placed:
            self._grid.mines.place_mines(pos_x, pos_y)
            self._mines_placed = True
        if cell.reveal():
            return True
        if cell.cellstate.mines_around == 0:
            self._reveal_adjacent(pos_x, pos_y)
        return False

    def _reveal_adjacent(self: Self, pos_x: int, pos_y: int) -> None:
        """Reveal all adjacent cells around the specified position.

        This method reveals all cells in the 3x3 area around the cell at (pos_x, pos_y),
        excluding the cell itself. It checks if each adjacent cell can be revealed
        (not a mine and not already revealed) before revealing it.

        Args:
            pos_x (int): x-coordinate of the cell to check.
            pos_y (int): y-coordinate of the cell to check.
        """
        for dx in (-1, 0, 1):
            for dy in (-1, 0, 1):
                nx, ny = pos_x + dx, pos_y + dy
                if self._can_reveal(nx, ny):
                    self._reveal_cell(nx, ny)

    def _reveal_all_mines(self: Self) -> None:
        """Reveal all mines on the grid.

        This method reveals all cells that contain mines. It is typically called when
        the player hits a mine, to show all mines on the grid.
        """
        for row in self._grid.cells:
            for cell in row:
                if cell.cellstate.is_mine:
                    cell.reveal()

    def _can_reveal(self: Self, pos_x: int, pos_y: int) -> bool:
        """Check if a cell can be revealed.

        This method checks if the cell at (pos_x, pos_y) can be revealed. A cell can be
        revealed if it is within the bounds of the grid, not already revealed, and not a
        mine.

        Args:
            pos_x (int): x-coordinate of the cell to check.
            pos_y (int): y-coordinate of the cell to check.

        Returns:
            bool: True if the cell can be revealed, False otherwise.
        """
        in_bounds_x: bool = 0 <= pos_x < self._grid.cell_num
        in_bounds_y: bool = 0 <= pos_y < self._grid.cell_num
        if not (in_bounds_x and in_bounds_y):
            return False

        cell: Cell = self._grid.cells[pos_x][pos_y]
        return not cell.cellstate.is_revealed and not cell.cellstate.is_mine

    def _reveal_cell(self: Self, pos_x: int, pos_y: int) -> None:
        """Reveal a cell and its adjacent cells if necessary.

        This method reveals the cell at (pos_x, pos_y) and, if it has no adjacent mines,
        it recursively reveals all adjacent cells.

        Args:
            pos_x (int): x-coordinate of the cell to reveal.
            pos_y (int): y-coordinate of the cell to reveal.
        """
        cell: Cell = self._grid.cells[pos_x][pos_y]
        cell.reveal()
        if not cell.cellstate.mines_around:
            self._reveal_adjacent(pos_x, pos_y)


class Grid:
    """Manage the grid of cells in the Minesweeper game.

    This class manages the cells of the Minesweeper game, including mine placement and
    revealing cells. It also includes managers for mine placement and cell revealing.

    Attributes:
        _cell_size (int): Size of each cell in pixels.
        cell_num (int): Number of cells in one dimension (grid is square).
        mines_count (int): Total number of mines to place on the grid.
        _shift (float): Vertical shift for the grid, used for UI layout.
        cells (list[list[Cell]]): A 2D list of Cell objects representing the grid.
        mines_placed (bool): Flag indicating if mines have been placed on the grid.
        mines (MineManager): Manager for mine placement and counting around cells.
        reveal (RevealManager): Manager for revealing cells in the game.

    """

    def __init__(
        self: Self, cell_size: int, cell_num: int, mines_count: int, shift: float
    ) -> None:
        self._cell_size: int = cell_size
        self.cell_num: int = cell_num
        self.mines_count: int = mines_count
        self._shift: float = shift
        self.cells: list[list[Cell]] = [
            [
                Cell(pos_x, pos_y, self._cell_size, self._shift)
                for pos_y in range(self.cell_num)
            ]
            for pos_x in range(self.cell_num)
        ]

        self.mines: _MineManager = _MineManager(self)
        self.reveal: _RevealManager = _RevealManager(self)

    def draw(self: Self, screen: pygame.Surface) -> None:
        """Draw the grid of cells on the given screen.

        This method iterates through all cells in the grid and draws each cell on the
        specified screen.

        Args:
            screen (pygame.Surface): The surface on which to draw the grid of cells.
        """
        for row in self.cells:
            for cell in row:
                cell.draw(screen)

    def check_win_condition(self: Self) -> bool:
        """Check if the player has won the game.

        This method checks if all non-mine cells have been revealed. If all such cells
        are revealed, it returns True, indicating the player has won, otherwise it
        returns False.

        Returns:
            bool: True if the player has won, False otherwise.
        """
        for row in self.cells:
            for cell in row:
                if not cell.cellstate.is_mine and not cell.cellstate.is_revealed:
                    return False
        return True

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions