Давайте немного
глубже посмотрим в целом на парадигму программирования с использованием
функций, условий, циклов, различных типов данных. Все это в целом определяет
так называемое структурное программирование. И здесь важно не только уметь
формально использовать все те конструкции языка, о которых мы говорили на
протяжении наших занятий, но и научиться правильно строить архитектуру
программы. Давайте я покажу пример проектирования «сверху-вниз» на примере
создания игры «Сапер».
Вначале пару слов о
самой игре. Игрок может открывать любую клетку поля и если там нет мины, то
показывается число о количестве мин в прилегающих клетках. Цель – открыть все
поле не задев мин.
Как будем ее
программировать? Изначально перед нами пустой текстовый файл. И мы вначале
пропишем функцию запуска игры в целом:
def startGame():
"""Функция запуска игры: отображается игровое поле,
игрок открывает любую закрытую клетку,
результат проверяется на наличие мины или
выигрышной ситуации
"""
pass
Далее,
вызываем эту функцию:
startGame()
print("Игра завершена")
И когда она завершит
свою работу, то по нашему замыслу завершается и сама игра. Причем, сама функция
пока ничего не делает, там указан оператор pass,
который формально образует тело функции, но никаких действий не выполняет. У
нас получился самый высокий уровень в проектировании нашей игры. Своего рода –
первая итерация в создании программы. Причем, смотрите, вот эти многострочные
комментарии всегда может прочитать сторонний программист, или же вызвать в
консоли функцию:
для просмотра этой
подсказки. Рекомендуется такие комментарии писать во всех ключевых функциях
проектируемой программы.
Далее, программист
решает: ага, нужна вспомогательная функция для отображения игрового поля и выше
определяет ее:
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
в начале программы:
и далее, запишем
реализацию функции 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:
Следующая функция goPlayer будет иметь такую реализацию:
def goPlayer():
"""Функция для ввода пользователем координат
закрытой клетки игрового поля
"""
flLoopInput = True
while flLoopInput:
x, y = input("Введите координату через пробел: ").split()
if not x.isdigit() or not 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("Вы выиграли")
Теперь
мы видим результат игры. (Для более наглядного понимания создания игры смотрите видео этого занятия). Вот пример того как происходит проектирование программ
«сверху-вниз». В качестве примера попробуйте аналогичным образом реализовать
игру «крестики-нолики».