День добрый, читатели!
Здесь недавно жаловались, что тема “промышленного программирования” раскрыта недостаточно. Попытаюсь это исправить.
Для наглядности разберем как написать классическую змейку для контроллера семейства Siemens s7-300.
Если стало интересно – добро пожаловать под кат.
Внимание – картинки и много кода на подобном ассемблеру языке!
Вся программа выполняется в организационном блоке OB1, состоит из двух функциональных блоков FB10 и FB11, имеющих экземплярные блоки данных DB10 и 11.
Само игровое поле 10х10 клеток является двумерным массивом байт 10х10.
Для работы нашей змейки нужно решить небольшую проблему – нам необходим точный импульс, возникающий по времени. Можно использовать “Мигание битов”, это встроенная фича контроллера, но мы создадим свой пульс генератор FB10 с меркерами и структурами.
Во временных переменных OB1 можно найти много всего интересного, в этот раз нам пригодится время предыдущего цикла программы. За это время контроллер “переваривает” все, что скажут и выдает значения на выходы, затем считывает входы. Измеряется оно с довольно таки высокой точностью, и мы ему верим.
Как только набранное время становится больше или равно, генерируется импульс (он будет действовать только один цикл), по нему из накопленного значения отнимаем 1000 (вдруг у нас получится чуть больше 1000, обнулять поэтому нельзя) и в течение одного цикла контроллера у нас имеется положительный импульс.
Из этих импульсов очень просто можно сложить большие величины, например, 5 секунд.
Так же можно и минуты, часы, но это уже совсем другая история.
Обратите внимание, что нельзя одну и ту же переменную фронта использовать дважды, это даст очень сложно вылавливаемую ошибку в логике программы.
Теперь пришла очередь функционального блока самой змейки.
Входные переменные – команды движения влево, вправо, вверх, вниз и команда начала игры.
Если под рукой есть контроллер и модуль дискретного входа – можете повесить на эти переменные входы, к которым присоединены кнопки без фиксации. Получите полноценный игровой автомат. При особом желании и массив можно сделать из лампочек, но у меня за такое сразу же уволят =)
Первый пример – команда движения влево.
Если мы подали ее, не двигаемся вправо, это произошло только в данном цикле, то мы сбрасываем все предыдущие команды и объявляем движение влево.
FP #frnts.pos5
JCN done
R #snake.gameover
L 0
T #looper
OPN «massive»
LAR1 P#0.0
loop: L #looper
L 100
>=I
JC done
L 0
T DBB [AR1,P#0.0]
+AR1 P#1.0
L 1
L #looper
+I
T #looper
JU loop
done: NOP 0
Освобождается он простым заполнением нулями с 0 по 99ый элемент. Дело в том, что в STL нет работы с двумерными массивами при косвенной адресации, так что будем представлять этот массив в виде одномерного с 0 по 99ый элемент.
По старту мы переносим голову змеи в прямой адресации на элемент 9.5, делаем ее длину 2, даем команду ползти вверх, сбрасываем game over и даем команду на выброс еды в случайную точку игрового поля.
FP #frnts.pos6
JCN strt
L P#95.0
T #coordinate
L 5
T «massive».x[9].y[5]
L 2
T #tail_cut.lenght
S #move.up
R #snake.gameover
S #random.set_food
strt: NOP 0
Далее нам нужно сгенерировать еду для змейки. Каюсь, сам алгоритм генерации был мной подсмотрен на одной из первых ссылок гугля, на сайте «плк для добра».
Заключается он в том, что контроллер считает миллисекунды системного времени. Если взять это число, сложить со случайным, а потом отбросить лишнее — получаем генератор псевдослучайных чисел от 0 до заданной величины.
Далее, когда у нас выпали случайным образом Х и Y мы берем их и вычисляем номер элемента массива. Каждый шаг по X означает, что нужно передвинуться на следующий ряд элементов, то есть на 10, а каждый по Y означает движение от 0-элемента на 1.
В итоге элемент массива X[4]Y[7] превращается в 47ой элемент одномерного массива. Даем ему статус 7 — еда.
На случай если элемент занят — запускаем генератор снова.
FP #frnts.pos7
JCN food
repl: CALL «TIME_TCK»
RET_VAL:=#random.tick
L #random.tick
AD DW#16#1F
T #random.rot
L #random.tick
L #random.rot
RLD
L #random.tick
XOD
ABS
L 10
MOD
T #random.x
CALL «TIME_TCK»
RET_VAL:=#random.tick
L #random.tick
XOD DW#16#1E12F
T #random.rot
L #random.tick
L #random.rot
RLD
L #random.tick
XOD
ABS
L 10
MOD
T #random.y
L 0
T #looper
LAR1 P#0.0
posx: L #looper
L #random.x
>=I
JC next
L P#10.0
+AR1
L 1
L #looper
+I
T #looper
JU posx
next: L 0
T #looper
posy: L #looper
L #random.y
>=I
JC poss
L P#1.0
+AR1
L 1
L #looper
+I
T #looper
JU posy
poss: OPN «massive»
L DBB [AR1,P#0.0]
L 0
==I
JCN repl
L 7
T DBB [AR1,P#0.0]
food: R #snake.omnomnom
R #random.set_food
После удачного выброса еды змейка начинает свое движение, рассмотрим алгоритм на основе движения влево.
A #move.left
JCN ext1
OPN «massive»
LAR1 #coordinate
TAR1
L P#10.0
MOD
L P#0.0
==D
JCN ok_1
S #snake.gameover
JU gmov
ok_1: TAR1
L P#1.0
-D
LAR1
OPN «massive»
L DBB [AR1,P#0.0]
L 0
==I
JC nul1
L DBB [AR1,P#0.0]
L 7
==I
JC eat1
SET
S #snake.gameover
JU gmov
eat1: SET
S #snake.omnomnom
L #tail_cut.lenght
L 1
+I
T #tail_cut.lenght
nul1: L 3
T DBB [AR1,P#0.0]
TAR1 #coordinate
ext1: NOP 0
В этом алгоритме мы сразу выполняем несколько проверок. Делим на 10 — получаем остаток — номер в строке текущего элемента. Если мы двигаемся налево, находясь в нулевом элементе — конец игры.
То же самое произойдет, если на пути движения будет что-то кроме еды. Если наткнулись на еду — взводим бит, что только что поели, он запустит генератор, удлинняем хвост на 1.
Далее идет последний алгоритм — «Хвосторез». Он берет за основу последнюю координату, считывает команду на этой координате и идет в обратном порядке от головы змеи к хвосту. Если клетка выходит за пределы длинны — удаляем ее обнулением величины
AN #tail_cut.uncut
A «db_pulsegen».two_sec_pls
JCN nop
L 0
T #looper
L #coordinate
T #tail_cut.tmp_coordinate
lpct: L #looper
L #tail_cut.lenght
>I
JC cut
L #tail_cut.tmp_coordinate
LAR1
OPN «massive»
L DBB [AR1,P#0.0]
L 3
==I
JC m_lf
L DBB [AR1,P#0.0]
L 4
==I
JC m_rt
L DBB [AR1,P#0.0]
L 5
==I
JC m_up
L DBB [AR1,P#0.0]
L 6
==I
JC m_dn
JU nop
m_lf: L #tail_cut.tmp_coordinate
L P#1.0
+D
T #tail_cut.tmp_coordinate
L #looper
L 1
+I
T #looper
JU lpct
m_rt: L #tail_cut.tmp_coordinate
L P#1.0
-D
T #tail_cut.tmp_coordinate
L #looper
L 1
+I
T #looper
JU lpct
m_up: L #tail_cut.tmp_coordinate
L P#10.0
+D
T #tail_cut.tmp_coordinate
L #looper
L 1
+I
T #looper
JU lpct
m_dn: L #tail_cut.tmp_coordinate
L P#10.0
-D
T #tail_cut.tmp_coordinate
L #looper
L 1
+I
T #looper
JU lpct
cut: L #tail_cut.tmp_coordinate
LAR1
OPN «massive»
L 0
T DBB [AR1,P#0.0]
nop: NOP 0
Большое спасибо за внимание всем, кто дошел до конца статьи, надеюсь, было интересно.
Автор: Basyavr