Как выглядит интерфейс программы питон

Паттерн MVC на примере Cапера

Паттерн Model-View-Controller (MVC) является крайне полезным при создании приложений со сложным графическим интерфейсом или поведением. Но и для более простых случаев он также подойдет. В этой заметке мы создадим игру сапер, спроектированную на основе этого паттерна. В качестве языка разработки выбран Python, однако особого значения в этом нет. Паттерны не зависят от конкретного языка программирования и вы без труда сможете перенести получившуюся реализацию на любую другую платформу.

Коротко о паттерне MVC

Как следует из названия, паттерн MVC включает в себя 3 компонента: Модель, Представление и Контроллер. Каждый из компонентов выполняет свою роль и является взаимозаменяемым. Это значит, что компоненты связаны друг с другом лишь некими четкими интерфейсами, за которыми может лежать любая реализация. Такой подход позволяет подменять и комбинировать различные компоненты, обеспечивая необходимую логику работы или внешний вид приложения. Разберемся с теми функциями, которые выполняет каждый компонент.

Изучение TKinter за 8 минут / Разработка GUI программы на Python

Модель

Отвечает за внутреннюю логику работы программы. Здесь мы можем скрыть способы хранения данных, а также правила и алгоритмы обработки информации.

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

Представление

Отвечает за отображение данных Модели. На этом уровне мы лишь предоставляем интерфейс для взаимодействия пользователя с Моделью. Смысл введения этого компонента тот же, что и в случае с предоставлением различных способов хранения данных на основе нескольких Моделей.

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

Кроме того, следует учитывать, что в обязанности Представления входит лишь своевременное отображение состояния Модели. За обработку действий пользователя отвечает Контроллер, о которым мы сейчас и поговорим.

Контроллер

Обеспечивает связь между Моделью и действиями пользователя, полученными в результате взаимодействия с Представлением. Координирует моменты обновления состояний Модели и Представления. Принимает большинство решений о переходах приложения из одного состояния в другое.

Фактически на каждое действие, которое может сделать пользователь в Представлении, должен быть определен обработчик в Контроллере. Этот обработчик выполнит соответствующие манипуляции над моделью и в случае необходимости сообщит Представлению о наличии изменений.

Спецификации игры Сапер

Достаточно теории. Теперь перейдем к практике. Для демонстрации паттерна MVC мы напишем несложную игру: Сапер. Правила игры достаточно простые:

Как быстро сделать графический интерфейс на Python

  1. Игровое поле представляет собой прямоугольную область, состоящую из клеток. В некоторых клетках случайным образом расположены мины, но игрок о них не знает;
  2. Игрок может щелкнуть по любой клетке игрового поля левой или правой кнопками мыши;
  3. Щелчок левой кнопки мыши приводит к тому, что клетка будет открыта. При этом, если в клетке находится мина, то игра завершается проигрышем. Если в соседних клетках, рядом с открытой, расположены мины, то на открытой клетке отобразится счетчик с числом мин вокруг. Если же мин вокруг открытой клетки нет, то каждая соседняя клетка будет открыта по тому же принципу. То есть клетки будут открываться до тех пор, пока либо не упрутся в границу игрового поля, либо не дойдут до уже открытых клеток, либо рядом с ними не окажется мина;
  4. Щелчок правой кнопки мыши позволяет делать пометки на клетках. Щелчок на закрытой клетке помечает ее флажком, который блокирует ее состояние и предотвращает случайное открытие. Щелчок на клетке, помеченной флажком, меняет ее пометку на вопросительный знак. В этом случае клетка уже не блокируется и может быть открыта левой кнопкой мыши. Щелчок на клетке с вопросительным знаком возвращает ей закрытое состояние без пометок;
  5. Победа определяется состоянием игры, при котором на игровом поле открыты все клетки, за исключением заминированных.
Читайте также:
При обновлении версии программы возникла ошибка значение не является значением объектного типа имя

Пример того, что у нас получится приведен ниже:

minesweeper

UML-диаграммы игры Сапер

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

Диаграмма Состояний игровой клетки

Любая клетка на игровом поле может находиться в одном из 4 состояний:

  1. Клетка закрыта;
  2. Клетка открыта;
  3. Клетка помечена флажком;
  4. Клетка помечена вопросительным знаком.

Здесь мы определили лишь состояния, значимые для Представления. Поскольку мины в процессе игры не отображаются, то и в базовом наборе соответствующего состояния не предусмотрено. Определим возможные переходы из одного состояния клетки в другое с помощью UML Диаграммы Состояний:

minesweeper_cell_state_diagram

Диаграмма Классов игры Сапер

Поскольку мы решили создавать наше приложение на основе паттерна MVC, то у нас будет три основных класса: MinesweeperModel , MinesweeperView и MinesweeperController , а также вспомогательный класс MinesweeperCell для хранения состояния клетки. Рассмотрим их диаграмму классов:

minesweeper_class_diagram

Организация архитектуры довольно проста. Здесь мы просто распределили задачи по каждому классу в соответствии с принципами паттерна MVC:

  1. В самом низу иерархии расположен класс игровой клетки MinesweeperCell . Он хранит позицию клетки, определяемую рядом row и столбцом column игрового поля; одно из состояний state , которые мы описали в предыдущем подразделе; информацию о наличии мины в клетке ( mined ) и счетчик мин в соседних клетках counter . Кроме того, у него есть два метода: nextMark ( ) для циклического перехода по состояниям, связанным с пометками, появляющимися в результате щелчка правой кнопкой мыши, а также open ( ) , который обрабатывает событие, связанное с щелчком левой кнопкой мыши;
  2. Чуть выше расположен класс Модели MinesweeperModel . Он является контейнером для игровых клеток MinesweeperCell . Его первый метод startGame ( ) подготавливает игровое поле для начала игры. Метод isWin ( ) делает проверку игрового поля на состояние выигрыша и возвращает истину, если игрок победил, иначе возвращается ложь. Для проверки проигрыша предназначен аналогичный метод isGameOver ( ) . Методы openCell ( ) и nextCellMark ( ) всего лишь делегируют действия соответствующим клеткам на игровом поле, а метод getCell ( ) возвращает запрашиваемую игровую клетку;
  3. Класс Представления MinesweeperView включает следующие методы: syncWithModel ( ) — обеспечивает перерисовку Представления для отображения актуального состояния игрового поля в Модели; getGameSettings ( ) — возвращает настройки игры, заданные пользователем; createBoard ( ) — создает игровое поле на основе данных Модели; showWinMessage ( ) и showGameOverMessage ( ) соответственно отображают сообщения о победе и проигрыше;
  4. И наконец класс Контроллера MinesweeperController . В нем определено всего три метода на каждое возможное действие игрока: startNewGame ( ) отвечает за нажатие на кнопке «Новая игра» в интерфейсе Представления; onLeftClick ( ) и onRightClick ( ) обрабатывают щелчки по игровым клеткам левой и правой кнопками мыши соответственно.

Реализация игры Сапер на Python

Пришло время заняться реализацией нашего проекта. В качестве языка разработки выберем Python. Тогда класс Представления будем писать на основе модуля tkinter .

Но начнем с Модели.

Модель [crayon-64ab3dde0d659382177317-i/]

Реализация модели на языке Python выглядит следующим образом:

MIN_ROW_COUNT = 5
MAX_ROW_COUNT = 30
MIN_COLUMN_COUNT = 5
MAX_COLUMN_COUNT = 30
MIN_MINE_COUNT = 1
MAX_MINE_COUNT = 800
class MinesweeperCell :
# Возможные состояния игровой клетки:
# closed — закрыта
# opened — открыта
# flagged — помечена флажком
# questioned — помечена вопросительным знаком
def __init__ ( self , row , column ) :
self . row = row
self . column = column
self . state = ‘closed’
self . mined = False
self . counter = 0
markSequence = [ ‘closed’ , ‘flagged’ , ‘questioned’ ]
def nextMark ( self ) :
if self . state in self . markSequence :
stateIndex = self . markSequence . index ( self . state )
self . state = self . markSequence [ ( stateIndex + 1 ) % len ( self . markSequence ) ]
def open ( self ) :
if self . state ! = ‘flagged’ :
self . state = ‘opened’
class MinesweeperModel :
def __init__ ( self ) :
self . startGame ( )
def startGame ( self , rowCount = 15 , columnCount = 15 , mineCount = 15 ) :
if rowCount in range ( MIN_ROW_COUNT , MAX_ROW_COUNT + 1 ) :
self . rowCount = rowCount
if columnCount in range ( MIN_COLUMN_COUNT , MAX_COLUMN_COUNT + 1 ) :
self . columnCount = columnCount
if mineCount < self . rowCount * self . columnCount :
if mineCount in range ( MIN_MINE_COUNT , MAX_MINE_COUNT + 1 ) :
self . mineCount = mineCount
self . mineCount = self . rowCount * self . columnCount — 1
self . firstStep = True
self . gameOver = False
self . cellsTable = [ ]
for row in range ( self . rowCount ) :
for column in range ( self . columnCount ) :
cellsRow . append ( MinesweeperCell ( row , column ) )
self . cellsTable . append ( cellsRow )
def getCell ( self , row , column ) :
if row < 0 or column < 0 or self . rowCount < = row or self . columnCount < = column :
return None
return self . cellsTable [ row ] [ column ]
def isWin ( self ) :
for row in range ( self . rowCount ) :
for column in range ( self . columnCount ) :
cell = self . cellsTable [ row ] [ column ]
if not cell . mined and ( cell . state ! = ‘opened’ and cell . state ! = ‘flagged’ ) :
return False
return True
def isGameOver ( self ) :
return self . gameOver
def openCell ( self , row , column ) :
cell = self . getCell ( row , column )
if not cell :
cell . open ( )
if cell . mined :
self . gameOver = True
if self . firstStep :
self . firstStep = False
self . generateMines ( )
cell . counter = self . countMinesAroundCell ( row , column )
if cell . counter == 0 :
neighbours = self . getCellNeighbours ( row , column )
for n in neighbours :
if n . state == ‘closed’ :
self . openCell ( n . row , n . column )
def nextCellMark ( self , row , column ) :
cell = self . getCell ( row , column )
cell . nextMark ( )
def generateMines ( self ) :
for i in range ( self . mineCount ) :
while True :
row = random . randint ( 0 , self . rowCount — 1 )
column = random . randint ( 0 , self . columnCount — 1 )
cell = self . getCell ( row , column )
if not cell . state == ‘opened’ and not cell . mined :
cell . mined = True
def countMinesAroundCell ( self , row , column ) :
neighbours = self . getCellNeighbours ( row , column )
return sum ( 1 for n in neighbours if n . mined )
def getCellNeighbours ( self , row , column ) :
neighbours = [ ]
for r in range ( row — 1 , row + 2 ) :
neighbours . append ( self . getCell ( r , column — 1 ) )
if r ! = row :
neighbours . append ( self . getCell ( r , column ) )
neighbours . append ( self . getCell ( r , column + 1 ) )
return filter ( lambda n : n is not None , neighbours )

Читайте также:
Почему не работает программа савефром

В верхней части мы определяем диапазон допустимых настроек игры:

MIN_ROW_COUNT = 5
MAX_ROW_COUNT = 30
MIN_COLUMN_COUNT = 5
MAX_COLUMN_COUNT = 30
MIN_MINE_COUNT = 1
MAX_MINE_COUNT = 800

Вообще, эти настройки можно было сделать тоже частью Модели. Однако размеры поля и количество мин достаточно статичная информация и вряд ли будет часто меняться.

Затем мы определили класс игровой клетки MinesweeperCell . Она оказалась достаточно простой. В конструкторе класса происходит инициализация полей клетки значениями по умолчанию. Далее для упрощения реализации циклических переходов по состояниям мы используем вспомогательный список markSequence . Если клетка находится в состоянии ‘opened’ , которое не входит в этот список, то в методе nextMark ( ) ничего не произойдет, иначе клетка попадает в следующее состояние, причем, из последнего состояния ‘questioned’ она «перепрыгивает» в начальное состояние ‘closed’ . В методе open ( ) мы проверяем состояние клетки, и если оно не равно ‘flagged’ , то клетка переходит в открытое состояние ‘opened’ .

Далее следует определение класса Модели MinesweeperModel . Метод startGame ( ) осуществляет компоновку игрового поля по переданным ему параметрам rowCount , columnCount и mineCount . Для каждого из параметров происходит проверка на попадание в допустимый диапазон значений. Если переданное значение находится вне диапазона, то сохраняется то значение параметра игрового поля не меняется. Следует отметить, что для числа мин предусмотрена дополнительная проверка. Если переданное количество мин превышает размер поля, то мы ограничиваем его количеством клеток без единицы. Хотя, конечно, такая игра особого смысла не имеет и будет закончена в один шаг, поэтому вы можете придумать какое-нибудь свое правило на такой случай.

Игровое поле хранится в виде списка списков клеток в переменной cellsTable . Причем, обратите внимание, что в методе startGame ( ) у клеток устанавливается лишь значение позиции, но мины еще не расставляются. Зато определяется переменная firstStep со значением True . Это нужно для того, чтобы убрать элемент случайности из первого хода и не допускать мгновенный проигрыш. Мины будут расставляться после первого хода в оставшихся клетках.

Метод getCell ( ) просто возвращает клетку игрового поля по строке row и столбцу column . Если значение строки или столбца неверно, то возвращается None .

Метод isWin ( ) возвращает True , если все оставшиеся не открытые клетки игрового поля заминированы, то есть в случае победы, иначе вернется False . А метод isGameOver ( ) просто возвращает значение атрибута класса gameOver .

В методе openCell ( ) происходит делегирование вызова open ( ) объекту игровой клетки, которая расположена на игровом поле в позиции, указанной в параметрах метода. Если открытая клетка оказалось заминированной, то мы устанавливаем значение gameOver в True и выходим из метода. Если игра еще не окончена, то мы смотрим, а не первый ли это ход, проверяя значение firstStep . Если ход и правда первый, то произойдет расстановка мин по игровому полю с помощью вспомогательного метода generateMines ( ) , о которой мы поговорим немного позже. Далее мы подсчитываем количество заминированных соседних клеток и устанавливаем соответствующее значение атрибута counter для обрабатываемой клетки. Если счетчик counter равен нулю, то мы запрашиваем список соседних клеток с помощью метода getCellNeighbours ( ) и осуществляем рекурсивный вызов метода openCell ( ) для всех закрытых «соседей», то есть для клеток со статусом ‘closed’ .

Читайте также:
Какой тип документов в программе компас 3d предназначен для создания трехмерных изображений ответы

Метод nextCellMark ( ) всего лишь делегирует вызов методу nextMark ( ) для клетки, расположенной на переданной позиции.

Расстановка мин происходит в методе generateMines ( ) . Здесь мы просто случайным образом выбираем позицию на игровом поле и проверяем, чтобы клетка на этой позиции не была открыта и не была уже заминирована. Если оба условия выполнены, то мы устанавливаем значение атрибута mined равным True , иначе продолжаем поиск другой свободной клетки. Не забудьте, что для того, чтобы использовать на Python модуль random нужно явным образом его импортировать командой import random .

Метод подсчета количества мин countMinesAroundCell ( ) вокруг некоторой клетки игрового поля полностью основывается на методе getCellNeighbours ( ) . Запрос «соседей» клетки в методе getCellNeighbours ( ) тоже реализован крайне просто. Не думаю, что у вас возникнут с ним проблемы.

Представление [crayon-64ab3dde0d699737853164-i/]

Теперь займемся представлением. Код класса MinesweeperView на Python представлен ниже:

Источник: tech-geek.ru

Почему именно wxPython

Этим занятием мы с вами открываем серию видеоуроков по модулям языка Python.

Модули – это встроенные или сторонние разработки, которые существенно расширяют и упрощают функционал Python.

И начнем с модуля wxPython, который позволяет создавать оконный интерфейс наших программ. Конечно, подобных модулей множество. Например, из наиболее популярных и знакомых мне, следующие:

  • в разных операционных системах отображается «родной» интерфейс, т.к. wxWidgets использует родной API ОС, а не имитирует свой;
  • небольшой размер в компилируемых приложениях;
  • большое разнообразие виджетов для создания интерфейса;
  • без проблем создаются запускаемые файлы конечного приложения с помощью cx_Freeze (или аналогов).

import wx app = wx.App() frame = wx.Frame(None, title=’Hello World!’) frame.Show() app.MainLoop()

Если при запуске вы видите вот такое окно, значит wxPython был успешно установлен и готов к использованию. Прежде чем разбираться с текстом программы, давайте вначале я расскажу об общей концепции построения приложений на основе событий. Смотрите, когда мы распахиваем окно на весь экран, происходит определенное событие, если мы его минимизируем, происходит другое событие, если мы просто кликнем мышкой в области окна, произойдет третье событие, и так далее. То есть интерфейс реагирует на события. Условно это можно представить в виде вот такой событийной шкалы нашего приложения: В определенные моменты возникают различные события (красные точки) и они некоторое время выполняются (или, как говорят, обрабатываются). Эта обработка занимает некоторое время. То есть, с каждым событием связан определенный обработчик, попросту функция, которая и выполняет запрограммированные действия. Время работы функции – это фактически и есть время обработки события. После обработки событие пропадает и приложение переходит в режим ожидания следующего события. Когда оно возникает, снова выполняется его обработка, и, затем, оно также пропадает. Если в момент обработки события появляются другие события, то они помещаются в очередь и их обработка (в порядке следования) начнется после завершения текущего события. Наконец, при возникновении события WM_QUIT приложение завершает свою работу. Вот все эти операции по отслеживанию и обработке событий составляют главный цикл приложения, который реализуется вызовом метода: app.MainLoop() И обратите внимание, мы здесь отслеживаем все события приложения, а не только текущего окна. Программа может содержать несколько окон и события для каждого из них обрабатываются в этом главном цикле. Вот почему приведенная простейшая программа использует два разных класса:

  • App – класс общего функционала приложения;
  • Frame – класс отдельного окна (их может быть несколько).

Соответственно, в программе мы должны создать экземпляр класса App:

app = wx.App()
и позже запускаем для него главный цикл обработки событий:
app.MainLoop()

Источник: proproprogs.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru