Pull to refresh

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

Reading time 4 min
Views 15K
День добрый, хабражители!

Здесь недавно жаловались, что тема “промышленного программирования” раскрыта недостаточно. Попытаюсь это исправить.
Для наглядности разберем как написать классическую змейку для контроллера семейства 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
Comments 19
Comments Comments 19

Articles