Описание данного примера выполнено при участии студента СамГТУ Макарова А.С.
Данный пример показывает, как программа на основе микроконтроллера может быть смоделирована и осуществлена в Stateflow. Кроме того, данный пример иллюстрирует взаимодействие Stateflow с графическим интерфейсом пользователя. Запустить пример можно из окна Help Navigator, открыв его на закладке Demos.
"Tic Tac Flow" Модель Карманной Игры
Этот пример иллюстрирует, как программа на основе микроконтроллера может быть смоделирована и осуществлена используя Stateflow.
Модель разделена, чтобы отделить аппаратную-независимую часть (в данном случае, игровая логическая схема) от аппаратно-зависимой части (интерфейс пользователя).
Используются дескрипторы графики, чтобы формировать имитацию интерфейса пользователя высокого уровня, концепция изделия может быть проверена и усовершенствована через имитацию в цикле разработки прежде, чем разрабатывать или создавать какие-либо аппаратные средства.
Для выполненного конечного продукта, аппаратная-независимая часть создаётся Stateflow автоматически , в то время как аппаратно-зависимая часть заменена фактическим аппаратным и программным драйвером нижнего уровня.
Игровая логическая схема Stateflow | Имитация аппаратного ввода - вывода |
Эта Stateflow схема - правила игры. В конечном продукте, программное обеспечение для этой части игры автоматически сгенерировано Stateflow, не изменяя схему. |
Эта подсистема использует MATLAB, Маркер, Графические устройства, Simulink, и Stateflow
для имитации интерфейса пользователя карманной игры. В конечном продукте, эта подсистема заменяется электронными аппаратными средствами и программным драйвером низкого уровня. |
Эта модель может использоваться только для демонстрации , и не может быть использована ни для какой либо другой цели.
Игровая логическая схема Stateflow
Игровая логическая схема игры Tic Tac Flow состоит из 10 блоков
9 из которых ответственны за нажатия клавиш ( button_0 - button_8 )
10-ый блок TURN содержит функции управляющие процессом игры .
После запуска программы , переменнаой gameState (Массив размерностью 9) блока Turn присваивается значение 13 (см. ниже)
Кнопка №9 окрашивается в красный цвет (ход игрока 1)
Программа переходит в состояние ожидания нажатия любой кнопки игрового поля
После нажатия любой кнопки происходит событие EXT_PRESS
После того как игрок (человек ) сделал ход , управление передаётся функции choose_move() 1. Функция choose_move() Выходная переменная - move имеет тип int8 (8-ми разрядное целое) , передающее своё значение в одноимённую входную переменную move функции make_move_if_available() С помощью этой функции ( choose_move() ) программа выбирает свой дальнейший ход ,в функцию make_move_if_available() передаётся значение переменной move make_move_if_available(move)
Рассмотри подробнее эту ключевую функцию
Функция choose_move() состоит из 3 ступеней
1 Итерация - "Попытка выиграть этим ходом"
С помощью формулы : { move = 12 - gameState[1] - gameState[3] } пытается определить выигрышный ход для себя, для этого находит недостающее до 12 число в диапазоне [0-8] и если оно не занято (пустая клетка)- это означает что игрок (компьютер)
Победил .
*12 - это контрольная сумма ( т.е сумма значений нажатых кнопок необходимых для победы )
**Так как ход ещё не выполнен , а только готовится и смещение ходов ещё не произошло, используются не gameState[0] , gameState[2] (обозначающие состояния ходов текущего игрока ) а gameState[1] , gameState[3] свои ходы , сделанные ранее)
Рассмотрим соответствующую ситуацию на примере.
Допустим у компьютера сложилась следующая ситуация , до победы ему не хватает заполнить одну клетку - левую нижнюю
Вот состояние его ходов : gameState[1]=5; gameState[3]=4 (до смещения)
По формуле { move = 12 - gameState[1] - gameState[3] } определяется выигрышный ход move
Move = 12 - 5 - 4 = 3 , он равен 3
Далее отправляется на проверку в функцию Make_move_if_available(3) , в данном примере функция вернула значение "истина".
Компьютер совершает победный ход.
Иначе если данный ход сделать было не возможно - скажем, эта клетка была уже заполнена противником
Переходим к ступени 2
2 Итерация - "Не дать сопернику победить"
Тут всё наоборот если нет возможности победить , с этого хода - компьютер пытается не дать победить игроку - для этого он вычисляет не хватающее число до контрольной суммы (12)
С помощью формулы { move = 12 - gameState[0] - gameState[2] } ищет недостающее число в диапозоне [0-8] если оно свободно то делает ход
Рассмотрим пример: Предположим у игрока следующая ситуация : gameState[0]=4 ; gameState[2]=1 , для победы ему необходимо заполнить
левый верхний квадрат (эта кнопка со значением = 7 , именно этого и недостаёт до 12-ти)
Что делает компьютер чтобы предотвратить это : он сам вычисляет с помощью простенькой формулы это значение и заполняет его :
move = 12 - gameState[0] - gameState[2] , находит чему равен move
move = 12 - 4 - 1 =7 , это значение и передаётся в функцию make_move_if_available(7) , а затем уже эта функция решает возможен этот ход или нет , в данном примере это возможно сделать.
Если эта клетка занята , или противник не находится в ситуации когда для победы ему нужно заполнить всего одну клетку то переходим к ступени 3
3 Итерация - если дошло до этой ступени, значит ход не критический ( нет опасности выиграть или проиграть )
Тут происходит генерирование линейной псевдо-случайной последовательности ( хода ) [0-8],
цикл повторяется пока истинно условие : [!make_move_if_available(move)]
другими словами она перебирает значения пока не найдёт первую попавшуюся пустую клетку.
2. Функция make_move_if_available()
В этой функции происходит проверка условия по Входной переменной Move передаваемая из функции choose_move() и опрос состояния кнопки button_#.enabled если условие выполняется хотя бы в одном из узлов - происходит :
1) Вызов функции shift_game_state(move)
2) Вызов события send(PRESS,button_#); (Вызов события PRESS для соответствующей кнопки)
Затем выполняется выход из функции и функция возвращает 1 (присваивается переменной Yes) иначе
функция возвращает 0
3. Функция shift_game_state()
Далее управление передаётся функции shift_game_state(move)
Эта функция модифицирует традиционную игру "крестики-нолики".
Смысл заключается в том что каждый игрок может оперировать только 3-я крестиками или ноликами .
Если каждый из игроков уже заполнил по 3 клетки то происходи смещение , то есть ход gameState[5] приобретает новое значение равное gameState[4] , а новый ход (gameState[0]) приобретает значение вновь нажатой кнопки :
{
gameState[5]= gameState[4]
::::::::::::.
gameState[0]=номер_нажатой_кнопки=move=magicNumber
}
Элементы массива [6-8] в процессе игры не участвуют , полученное ими значение (13) при запуске , в ходе игры не изменяется , видимо, разработчики сделали массив gameState размерностью 9 чтобы можно было легко перейти к классической игре "Крестики-нолики"
Блоки Button_0 - Button_8 - имеют одинаковую структуру.
Константа thisButton имеет значение, соответствующее номеру кнопки.
Каждая кнопка имеет 3 состояния :
1) Enabled - Доступна
2) Disabled - Недоступна
3) Active - Активна (нажата, имеет цвет отличный от чёрного)
При запуске программы все из 9-ти кнопок переходят в состояние Enabled , цвет кнопок чёрный При нажатии на кнопку button_# проверяется чей ход - Red или Green и исходя из этого окрашивает клетку в соответствующий цвет. Если после нажатия игра заканчивается, все кнопки переходят в состояние Disabled и цвет проигравшей стороны темнеет (GREEN=>DIM_GREEN: - константы) Если кнопка находилась в состоянии Active и at(7,TURN) (тоесть 6 кнопок уже находятся в состоянии Active) то эта кнопка переходит в состояние Enabled и окрашивается в чёрный цвет.
Функция is_a_winner().
В случае если игра окончена переменой Yes (тип Boolean) присваивается значение 1 иначе 0.
Функция вызывается после каждого хода игроков и если Yes = 1 вызывается событием Game_Over , игра заканчивается - все кнопки игрового поля переходят в состояние DISABLED
GameState - это глобальная переменная (Числовой массив - размерностью 9) в пределах всего блока Turn
Он являет собой состояние игры.
Функция предназначена для проверки заполнены ли 3 последовательных диагональных , вертикальных или горизонтальных клетки.
**Заметим то что только одна функция служит для проверки выигрыша и игрока 1 и игрока 2 - для функции совершенно не важно чей сейчас ход ей достаточно знать состояния текущего хода , тесть значения следующих индексов массива gameState : [0] [2] [4]
Как так ? Это становится возможным благодаря смещению ходов описанный выше , проще говоря gameState[0],[2],[4] это состояние ходов текущего игрока , который в данный момент выполнил ход и ожидает результата , а gameState[1],[3],[5] это ходы предыдущего игрока
Кнопки расположены в следующем порядке , как показано на рисунке - каждая кнопка имеет свою константу ThisButton равная её номеру , сразу бросается в глаза , почему клавиши расположены в таком , казалось бы не правильном порядке , ответ очевиден - если подсчитать сумму любой последовательность 3 клеток (выигрышную последовательность) она равна 12 - именно это и является необходимым условием победы
yes =((gameState[0] + gameState[2] + gameState[4]) == 12);
Что бы экспериментально доказать это я провёл серию экспериментов , для этого я ввёл дополнительную глобальную переменную (Массив размерностью 9) передающую значения локальной переменной gameState в Simulink модель , и отобразил её с помощью блока Display , который отображал состояния всех индексов массива
Действительно , сумма GameState[0]+GameState[2]+GameState[4]=3+8+1=12
GameState[1]+GameState[3]+GameState[5]=4+5+6=15
Как говорилось выше при старте симуляции переменной gameState(кажному индексу) было присвоено значение 13 (оно могло быть и любой другой , главное больше 12)