Pull to refresh

Змейка на ПЛК? Легко!

Reading time4 min
Views15K
День добрый, хабражители!

Здесь недавно жаловались, что тема “промышленного программирования” раскрыта недостаточно. Попытаюсь это исправить.
Для наглядности разберем как написать классическую змейку для контроллера семейства Siemens s7-300.

image

Если стало интересно – добро пожаловать под кат.
Внимание – картинки и много кода на подобном ассемблеру языке!

Вся программа выполняется в организационном блоке OB1, состоит из двух функциональных блоков FB10 и FB11, имеющих экземплярные блоки данных DB10 и 11.



Само игровое поле 10х10 клеток является двумерным массивом байт 10х10.



Для работы нашей змейки нужно решить небольшую проблему – нам необходим точный импульс, возникающий по времени. Можно использовать “Мигание битов”, это встроенная фича контроллера, но мы создадим свой пульс генератор FB10 с меркерами и структурами.

Во временных переменных OB1 можно найти много всего интересного, в этот раз нам пригодится время предыдущего цикла программы. За это время контроллер “переваривает” все, что скажут и выдает значения на выходы, затем считывает входы. Измеряется оно с довольно таки высокой точностью, и мы ему верим.



Как только набранное время становится больше или равно, генерируется импульс (он будет действовать только один цикл), по нему из накопленного значения отнимаем 1000 (вдруг у нас получится чуть больше 1000, обнулять поэтому нельзя) и в течение одного цикла контроллера у нас имеется положительный импульс.



Из этих импульсов очень просто можно сложить большие величины, например, 5 секунд.



Так же можно и минуты, часы, но это уже совсем другая история.

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

Теперь пришла очередь функционального блока самой змейки.



Входные переменные – команды движения влево, вправо, вверх, вниз и команда начала игры.
Если под рукой есть контроллер и модуль дискретного входа – можете повесить на эти переменные входы, к которым присоединены кнопки без фиксации. Получите полноценный игровой автомат. При особом желании и массив можно сделать из лампочек, но у меня за такое сразу же уволят =)

Первый пример – команда движения влево.



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

Далее при старте игры мы освобождаем массив
A #snake.start
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 и даем команду на выброс еды в случайную точку игрового поля.

старт игры
A #snake.start
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 — еда.

На случай если элемент занят — запускаем генератор снова.

генератор еды
A #random.set_food
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 «db_pulsegen».two_sec_pls
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.

Далее идет последний алгоритм — «Хвосторез». Он берет за основу последнюю координату, считывает команду на этой координате и идет в обратном порядке от головы змеи к хвосту. Если клетка выходит за пределы длинны — удаляем ее обнулением величины

режем хвосты
A #snake.start
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

Большое спасибо за внимание всем, кто дошел до конца статьи, надеюсь, было интересно.

Ссылка на скачивание проекта
Tags:
Hubs:
+25
Comments19

Articles

Change theme settings