Проектирование программ

Проектирование программ "сверху-вниз"

Давайте немного глубже посмотрим в целом на парадигму программирования с использованием функций, условий, циклов, различных типов данных. Все это в целом определяет так называемое структурное программирование. И здесь важно не только уметь формально использовать все те конструкции языка, о которых мы говорили на протяжении наших занятий, но и научиться правильно строить архитектуру программы. Давайте я покажу пример проектирования «сверху-вниз» на примере создания игры «Сапер».

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

 

Как будем ее программировать? Изначально перед нами пустой текстовый файл. И мы вначале пропишем функцию запуска игры в целом:

def startGame():

    """Функция запуска игры: отображается игровое поле,

        игрок открывает любую закрытую клетку,

        результат проверяется на наличие мины или

        выигрышной ситуации

    """


    pass

Далее, вызываем эту функцию:

startGame()

print("Игра завершена")

И когда она завершит свою работу, то по нашему замыслу завершается и сама игра. Причем, сама функция пока ничего не делает, там указан оператор pass, который формально образует тело функции, но никаких действий не выполняет. У нас получился самый высокий уровень в проектировании нашей игры. Своего рода – первая итерация в создании программы. Причем, смотрите, вот эти многострочные комментарии всегда может прочитать сторонний программист, или же вызвать в консоли функцию:

help(startGame)

для просмотра этой подсказки. Рекомендуется такие комментарии писать во всех ключевых функциях проектируемой программы.

Далее, программист решает: ага, нужна вспомогательная функция для отображения игрового поля и выше определяет ее:

def show():

    """Функция отображения состояния текущего

        игрового поля

    """


    pass

Пока она тоже ничего не делает, т.к. идет процесс создания архитектуры программы в целом. И раз есть функция отображения поля, то это поле нужно где-то создавать (располагать мины). Появляется функция:

def createGame():

    """Создание игрового поля: расположение мин

        и подсчет числа мин вокруг клеток без мин

    """


    pass

Затем, на этом же уровне проектирования нам, очевидно, понадобится функция для ввода координат клетки игроком:

def goPlayer():

    """Функция для ввода пользователем координат

        закрытой клетки игрового поля

    """


    pass

Далее, функция проверки текущего состояния игры:

def isFinish():

    """Определение текущего состояния игры:

        выиграли, проиграли, игра продолжается

    """


    pass

Все, мы определили необходимые вызываемые функции и теперь подумаем: как их вызывать в функции верхнего уровня startGame? Очевидно, должен быть цикл, в котором будет происходить последовательный их вызов. Допустим, мы это делаем самым простым образом, вот так:

def startGame():

    """Функция запуска игры: отображается игровое поле,

        игрок открывает любую закрытую клетку,

        результат проверяется на наличие мины или

        выигрышной ситуации

    """


    while isFinish():

        show()

        goPlayer()

Отлично, общий вид программы у нас определен:

Пришла пора наполнить эти функции конкретным содержимым. Программист далее думает: у нас должно быть два списка: один с расположением мин и с числовыми значениями, а второй для отображения текущего состояния игры:

Одно из них пусть называется PM, а второе – P. Далее, нужно решить где создать эти переменные: внутри функции startGame или глобальными в начале текста программы. Так как глобальных переменных в любой программе должно быть минимум, то объявим их внутри startGame:

    P = [-2]*N*N

    PM = [0]*N*N

Здесь у поля P значение -2 будет говорить о том, что клетка еще не открыта. Значение -1 у поля PM – стоит мина, а любое неотрицательное число у обоих списков – открытая клетка без мины и указанием мин вокруг нее.

А вот величину N – размер игрового поля и число мин M на игровом поле, сделаем для простоты глобальными:

N, M = (5, 10) # размер игрового поля NxN и число мин M

Теперь у нас все готово для реализации первой функции createGame. Сначала мы произвольным образом расставим мины на поле PM. И договоримся так: если клетка имеет значение >= 0, то мины в ней нет и число показывает количество мин вокруг текущей клетки. А любое отрицательное значение – мина есть. Для генерации случайных значений подключим модуль random в начале программы:

import random

и далее, запишем реализацию функции createGame. Ей понадобится доступ к переменной PM, добавим ее в качестве аргумента:

def createGame(PM):

и, затем, в самой функции:

    rng = random.Random()

    n = M

    while n>0:

        i = rng.randrange(N)  # случайное целое [0; N)

        j = rng.randrange(N)

        if PM[i*N+j] != 0:

            continue

        PM[i*N+j] = -1

        n -= 1

Далее, в этой же функции рассчитаем число мин вокруг каждой клетки без мин:

    # вычисляем количество мин вокруг клетки

    for i in range(N):

        for j in range(N):

            if PM[i*N+j] >= 0:

                PM[i*N+j] = getTotalMines(PM, i, j)

Здесь мы создадим вспомогательную функцию getTotalMines:

def getTotalMines(PM, i, j):

    n = 0

    for k in range(-1,2):

        for l in range(-1,2):

            x = i+k

            y = j+l

            if x < 0 or x >= N or y < 0 or y >= N:

                continue

            if PM[x*N+y] < 0:

                n += 1

 

    return n

Все, наша функция createGame готова. Далее, реализуем функцию show и с ее помощью проверим: правильно ли работает createGame:

def show(pole):

    """Функция отображения состояния текущего

        игрового поля

    """


 

    for i in range(N):

        for j in range(N):

            print( str(pole[i*N+j]).rjust(3), end="" )

        print()

И вызовем ее в startGame:

    createGame(PM)

    show(PM)

Следующая функция goPlayer будет иметь такую реализацию:

def goPlayer():

    """Функция для ввода пользователем координат

        закрытой клетки игрового поля

    """


    flLoopInput = True

    while flLoopInput:

        x, y = input("Введите координату через пробел: ").split()

        if ot x.isdigit() or ot y.isdigit():

            print("Координаты введены неверно")

            continue

       

        x = int(x)-1

        y = int(y)-1

 

        if x < 0 or x >= N or y < 0 or y >= N:

            print("Координаты выходят за пределы поля")

            continue

 

        flLoopInput = False

 

    return (x, y)

Проверим работу этой функции:

    createGame(PM)

    show(PM)

    goPlayer()

Осталось реализовать последнюю функцию isFinish. Здесь договоримся так: если функция возвращает:

  • 1 – игра продолжается;
  • -1 – игрок наступил на мину и игра проиграна;
  • -2 – игрок открыл все клетки без мин – игра выиграна.

Для реализации функции нам потребуются переменные P и PM:

def isFinish(PM, P):

    """Определение текущего состояния игры:

        выиграли, проиграли, игра продолжается

    """


    for i in range(N*N):

        if P[i] != -2 and PM[i] < 0: return -1

        if P[i] == -2 and PM[i] >= 0: return 1

 

    return -2

И окончательно запишем тело функции startGame:

def startGame():

    """Функция запуска игры: отображается игровое поле,

        игрок открывает любую закрытую клетку,

        результат проверяется на наличие мины или

        выигрышной ситуации

    """


    P = [-2]*N*N

    PM = [0]*N*N

 

    createGame(PM)

    while isFinish(PM, P) > 0:

        show(P)

        x,y = goPlayer()

 

        P[x*N+y] = PM[x*N+y]

Если теперь запустить игру, то при ее завершении непонятно: выиграли мы или проиграли. Модифицируем немного функцию startGame:

def startGame():

    """Функция запуска игры: отображается игровое поле,

        игрок открывает любую закрытую клетку,

        результат проверяется на наличие мины или

        выигрышной ситуации

    """


    P = [-2]*N*N

    PM = [0]*N*N

 

    createGame(PM)

    finishState = isFinish(PM, P)

    while finishState > 0:

        show(P)

        x,y = goPlayer()

 

        P[x*N+y] = PM[x*N+y]

        finishState = isFinish(PM, P)

 

    return finishState

И, далее, ее вызов:

res = startGame()

if res == -1:

    print("Вы проиграли")

else:

    print("Вы выиграли")

Теперь мы видим результат игры. (Для более наглядного понимания создания игры смотрите видео этого занятия). Вот пример того как происходит проектирование программ «сверху-вниз». В качестве примера попробуйте аналогичным образом реализовать игру «крестики-нолики».

icon