Мине размашистая игра для терминала


Это очень простой (без возможности плитки флага) моя размашистая игра, написанная на Python и предназначенная для терминала. Из-за своей ограниченности не рекомендуется изменять размер платы в счет, что будет нарушать выравнивания визуальных индикаторов.

Он будет отображать на игровом поле, как показано ниже, и задать пользователю для ввода данных в виде м и Н до мин или все пустые плитки были найдены.
Выход из программы осуществляется путем ввода двух нечисловых значений, разделенных пробелами (это, вероятно, может быть сделано в более изящной манере, но я не знаю, как).

  0 1 2 3 4 5 6 7 8 9
  --------------------
0|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |0
1|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |1
2|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |2
3|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |3
4|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |4
5|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |5
6|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |6
7|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |7
8|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |8
9|~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |9
  --------------------
  0 1 2 3 4 5 6 7 8 9

Enter m n:

Я никогда не писал ничего существенного в Python, так что я очень заинтересован в какой-либо рекомендации в отношении наилучшей практики и другие вещи, которые я сделал неправильно/не подходящие для Python путь.

Код:

"""A simple minesweeping game"""

import os
from random import randint
import sys


class Tile:
    """Tile has a display value and can have a mine"""

    def __init__(self, value):
        """Tile constructor"""

        self.value = value
        self.has_mine = False


class Board:
    """Board has a collection of tiles and provides methods
    to interact with them
    """

    def __init__(self, width, height, mines, closed_tile, open_tile):
        """Board constructor"""

        self.width = width
        self.height = height
        self.mines = mines
        self.closed_tile = closed_tile
        self.open_tile = open_tile
        self.tiles = []
        self.uncovered_tiles = 0

    def initialize(self):
        """Create the board with initial values and randomly place mines"""

        for i in range(self.height):
            sublist = []
            for j in range(self.width):
                sublist.append(Tile(self.closed_tile))
            self.tiles.append(sublist)

        i = 0
        while i < self.mines:
            m, n = randint(0, self.height - 1), randint(0, self.width - 1)
            if not self.tiles[m][n].has_mine:
                self.tiles[m][n].has_mine = True
                i += 1

    def display(self):
        """Print the board along with coordinate indicators"""

        os.system('cls' if os.name == 'nt' else 'clear')

        print("  " + " ".join(str(i) for i in range(0, self.width)))
        print("  " + "-" * (self.width * 2))

        i = 0
        for col in range(len(self.tiles)):
            sys.stdout.write(str(i) + "|")
            for row in range(len(self.tiles[col])):
                print(self.tiles[col][row].value, end=" ")
            print("|" + str(i))
            i += 1

        print("  " + "-" * (self.width * 2))
        print("  " + " ".join(str(i) for i in range(0, self.width)))

    def count_mines(self, m, n):
        """Count the mines adjacent to the given tile"""

        mines = 0
        for y in range(m - 1, m + 2):
            for x in range(n - 1, n + 2):
                if (y < 0 or y >= self.height or x < 0 or x >= self.width):
                    continue

                if self.tiles[y][x].has_mine:
                        mines += 1

        return mines

    def check_tile(self, m, n):
        """Check tiles recursively"""

        if self.tiles[m][n].value != self.closed_tile:
            return

        self.uncovered_tiles += 1

        mines = self.count_mines(m, n)
        if mines > 0:
            self.tiles[m][n].value = mines
            return

        self.tiles[m][n].value = self.open_tile

        for y in range(m - 1, m + 2):
            for x in range(n - 1, n + 2):
                if (x == n and y == m):
                    continue

                if (y < 0 or y >= self.height or x < 0 or x >= self.width):
                    continue

                self.check_tile(y, x)

    def is_board_uncovered(self, goal):
        """Check if all tiles except for mines have been uncovered"""

        return self.uncovered_tiles == goal

    def tile_has_mine(self, m, n):
        """Determine if a user-picked tile contains a mine or not"""

        return self.tiles[m][n].has_mine


class Game:
    """Game provides the main loop and passes user input to board"""

    def __init__(self):
        """Game constructor"""

        width = 20
        height = 20
        mines = 10
        closed_tile = "~"
        open_tile = " "
        self.board = Board(width, height, mines, closed_tile, open_tile)
        self.goal = width * height - mines
        self.board.initialize()

    def play(self):
        """Play the game until a win/lose condition is encountered"""

        while True:
            self.board.display()

            m, n = self.get_input()

            if self.board.tile_has_mine(m, n):
                print("Game Over")
                sys.exit(0)

            self.board.check_tile(m, n)

            if self.board.is_board_uncovered(self.goal):
                print("You Won\n")
                sys.exit(0)

    def get_input(self):
        """make sure the user enters 2 valid numbers or quit"""

        while True:
            try:
                m, n = (input("\nEnter m n: ").split())
            except:
                print("Invalid number of arguments")
                continue

            try:
                m = int(m)
                n = int(n)
            except:
                sys.exit(0)

            if (m >= 0 and m < self.board.height and
               n >= 0 and n < self.board.width):
                return m, n
            else:
                print("Invalid range")


def main(argv):
    game = Game()
    game.play()


if __name__ == "__main__":
    main(sys.argv)


217
8
задан 11 апреля 2018 в 01:04 Источник Поделиться
Комментарии
1 ответ

Что касается именования, я бы сказал, что код-это очень хорошо, После большинство PEP8 гидов. Код также документально подтверждено, что является большим плюсом.

Весть

Есть некоторые вещи, которые вы можете сделать, чтобы сделать код более подходящие для Python:


  • Прикованный Сравнения

    Рассмотрим следующее сравнение в коде:

    if (m >= 0 and m < self.board.height and
    n >= 0 and n < self.board.width):

    Вы можете превратить его в:

    if 0 <= m < self.board.height and 0 <= n < self.board.width:

    Что является более подходящие для Python способ сделать то же самое. Обратите внимание, как я также удалены
    скобках, так как они являются избыточными и только загромождает код. Также обратите внимание, что эти дополнительные скобки появляются в других местах кода.


  • Списочные Включения

    Есть несколько мест, где их можно использовать, например, initialize:

    for i in range(self.height):
    sublist = []
    for j in range(self.width):
    sublist.append(Tile(self.closed_tile))
    self.tiles.append(sublist)

    Могут быть преобразованы в:

    for i in range(self.height):
    sublist = [Tile(self.closed_tile) for _ in range(self.width)]
    self.tiles.append(sublist)

    Вы можете даже пойти так далеко, как делаю:

    for i in range(self.height):
    self.tiles.append([Tile(self.closed_tile) for _ in range(self.width)])

    Или:

    self.tiles = [[Tile(self.closed_tile) for i in range(self.width)] for j in range(self.height)]

    Но как вы можете видеть в этом последнем случае, он начинает становится все труднее читать, поэтому лично я бы оставил как один из первых двух вариантов, которые я показал.

    Есть и другие места, где их можно использовать.


  • Пользу интерполяции по конкатенации

    У вас есть только две ситуации, когда вы соеденение и даже эти настолько просты, что в основном нормально, как и они. Рассмотрим этот:

    print("  " + " ".join(str(i) for i in range(0, self.width)))
    print(" " + "-" * (self.width * 2))

    Можно даже спорить, если они должны быть изменены. Тем не менее, интерполяция легче в большинстве случаев, если не все. Вы могли бы сделать их вот так:

    print("  {}".format(" ".join(str(i) for i in range(0, self.width))))
    print(" {}".format("-" * (self.width * 2)))

Исключения

Не простудиться исключения с голой except так как он слишком широк:

try:
m = int(m)
n = int(n)
except:

В этом случае неудачного преобразования поднимут ValueError исключение. Таким образом, Вы сможете регулировать except захватить его конкретно:

try:
m = int(m)
n = int(n)
except ValueError:

Это другое дело:

while True:
try:
m, n = (input("\nEnter m n: ").split())
except:
print("Invalid number of arguments")
continue

Нет необходимости исключение здесь, и вы можете проверить напрямую, если split результаты 2-х элементов:

while True:
coordinates = input("\nEnter m n: ").split()
if len(coordinates) != 2:
print("Invalid number of arguments")
continue

m, n = coordinates

Я держал оба m и n так что ее легче увидеть, как она вписывается в остальной код, хотя я предпочла бы более осмысленные имена.

Сыс.выход

А sys.exit может быть нормально и даже удобно, некоторые очень мелкие скрипты, это становится трудно управлять в крупных приложениях. Также они, как правило, сделать код сложнее отлаживать, потому что есть много разрозненных точек выхода.

Как ни странно, большинство из тех, которые вы использовали в коде не надо для логика Вы были после. В play метод-это единственный метод, называемый в main функции, поэтому выход в данном случае-это буквально эквивалентно sys.exitС вами было.

Так что вы могли бы переписать его:

def play(self):
"""Play the game until a win/lose condition is encountered"""

while True:
self.board.display()
m, n = self.get_input()

if self.board.tile_has_mine(m, n):
print("Game Over")
break # instead of sys.exit(0)

self.board.check_tile(m, n)
if self.board.is_board_uncovered(self.goal):
print("You Won\n")
break # instead of sys.exit(0)

Который имеет точно такой же смысл в данном случае.

Пока я здесь позвольте мне сделать еще два дополнительных пояснений по этому методу:


  • В printиспользовала не прекращение, поскольку во втором трейлинг \n. Последовательность является ключевым

  • board.is_board_uncovered заканчивается повторяя это слово. Лучше и проще будет board.is_uncovered

Вы можете фактически избежать sys.exit вообще, если вы оба структуру play и get_input по-разному, и исключения, когда входной сигнал не удалось прочитать:

def play(self):
"""Play the game until a win/lose condition is encountered"""

self.board.display()
while True:
try:
m, n = self.get_input()
except ValueError:
break # non-numeric exit
except Exception as ex:
print(ex)
continue

self.board.display()
if self.board.tile_has_mine(m, n):
print("Game Over")
break

self.board.check_tile(m, n)
if self.board.is_board_uncovered(self.goal):
print("You Won")
break

def get_input(self):
"""make sure the user enters 2 valid numbers or quit"""

coordinates = input("\nEnter m n: ").split()
if len(coordinates) != 2:
raise Exception("Invalid number of arguments")

m, n = map(int, coordinates)
if m < 0 or m >= self.board.height or n < 0 or n >= self.board.width:
raise Exception("Invalid range")

return m, n

Для этого решения get_input только возвращает входные данные, если они были действительны, в противном случае он вызывает исключение. Он также устраняет необходимость в while петли в get_input. В play метод, если исключение поймано это означает, что вход не был захвачен должным образом, и поэтому он запрашивает его снова переходить к следующей итерации.

Выход из игры


Выход из программы осуществляется путем ввода двух нечисловых
значений, разделенных пробелами (это может быть сделано в более изящные
порядке, но я не знаю, как)

На самом деле программа завершает работу при одном из заданных значений не является числовым, а не обе.

Кроме того, я не вижу слишком много других способов сделать это, и это, несомненно, будет проще с GUI, у которого есть кнопка "Закрыть". Однако я бы предложил следующее:

В ожидании m и n входные значения, если пользователь нажимает клавишу Ввод дважды, вы выйдете из игры.

Это несколько более грациозно, чем текущее решение, а также предотвращает непреднамеренное введите нажмите. Это напоминает двойную кнопку назад на Android приложений, чтобы бросить их (по крайней мере на некоторые из них). С другой стороны, это решение будет означать немного больше логики в процессе обработки входной части.

6
ответ дан 12 апреля 2018 в 01:04 Источник Поделиться