# Snake on the PLC? Easy!

- From the sandbox
- Tutorial

Good day, harazhiteli!

It has recently been complained that the topic of “industrial programming” is not sufficiently addressed. I'll try to fix it.

For clarity, we will figure out how to write a classic snake for a controller of the Siemens s7-300 family.

If it became interesting - welcome to cat.

Attention - pictures and a lot of code in an assembler-like language!

The whole program is executed in the organizational block OB1, consists of two functional blocks FB10 and FB11, which have instance data blocks DB10 and 11.

The playing field 10x10 cells itself is a two-dimensional array of 10x10 bytes.

For the operation of our snake, we need to solve a small problem - we need an accurate impulse that arises in time. You can use “Flashing bits”, this is a built-in feature of the controller, but we will create our own pulse generator FB10 with merkers and structures.

You can find a lot of interesting things in the temporary variables OB1, this time we will need the time of the previous program cycle. During this time, the controller “digests” everything that they say and issues values to the outputs, then reads the inputs. It is measured with rather high accuracy, and we believe it.

As soon as the accumulated time becomes greater or equal, a pulse is generated (it will operate only one cycle), we subtract 1000 from it (suddenly we get a little more than 1000, therefore it is impossible to zero) and for one controller cycle we have a positive pulse.

From these pulses it is very easy to add large quantities, for example, 5 seconds.

You can also minutes, hours, but that's another story.

Now it's the turn of the functional block of the snake itself.

Input variables are left, right, up, down, and start commands.

If you have a controller and a discrete input module at hand, you can hang inputs on these variable inputs, to which buttons are attached without fixing. Get a full-fledged slot machine. With special desire, an array can also be made of light bulbs, but I’ll be fired immediately for this =)

The first example is a left move command.

If we filed it, we don’t move to the right, it happened only in this cycle, then we reset all previous commands and announce the movement to the left.

It is freed by simply filling in zeros from 0 to 99th element. The fact is that in STL there is no work with two-dimensional arrays with indirect addressing, so we will represent this array as a one-dimensional element from 0 to 99th element.

At the start, we transfer the snake’s head in direct addressing to element 9.5, make it 2 length, give the command to crawl up, reset the game over and give the command to throw food out to a random point on the playing field.

Next we need to generate food for the snake. I confess that the generation algorithm itself was spied on me at one of Google’s first links, on the PLC for Good website.

It consists in the fact that the controller counts milliseconds of system time. If we take this number, add it to a random one, and then discard the excess, we get a pseudo-random number generator from 0 to a given value.

Further, when X and Y fell out randomly, we take them and calculate the number of the array element. Each step along X means that you need to move to the next row of elements, that is, 10, and each along Y means moving from the 0-element to 1.

As a result, the element of the array X [4] Y [7] turns into the 47th element of the one-dimensional array . We give him status 7 - food.

In case the element is busy, we start the generator again.

After a successful ejection of food, the snake begins its movement, consider an algorithm based on left movement.

In this algorithm, we immediately perform several checks. Divide by 10 - we get the remainder - the number in the line of the current element. If we move to the left, being in the zero element - the end of the game.

The same thing happens if there is anything other than food in the way of movement. If you stumble upon food - cock the bit that you just ate, it will start the generator, extend the tail by 1.

Next is the last algorithm - “Tail Cutter”. He takes the last coordinate as a basis, reads the command at this coordinate and goes in the reverse order from the head of the snake to the tail. If the cell goes beyond the length - delete it by zeroing the value

Thank you very much for your attention to everyone who reached the end of the article, I hope it was interesting.

Link to download the project

It has recently been complained that the topic of “industrial programming” is not sufficiently addressed. I'll try to fix it.

For clarity, we will figure out how to write a classic snake for a controller of the Siemens s7-300 family.

If it became interesting - welcome to cat.

Attention - pictures and a lot of code in an assembler-like language!

The whole program is executed in the organizational block OB1, consists of two functional blocks FB10 and FB11, which have instance data blocks DB10 and 11.

The playing field 10x10 cells itself is a two-dimensional array of 10x10 bytes.

For the operation of our snake, we need to solve a small problem - we need an accurate impulse that arises in time. You can use “Flashing bits”, this is a built-in feature of the controller, but we will create our own pulse generator FB10 with merkers and structures.

You can find a lot of interesting things in the temporary variables OB1, this time we will need the time of the previous program cycle. During this time, the controller “digests” everything that they say and issues values to the outputs, then reads the inputs. It is measured with rather high accuracy, and we believe it.

As soon as the accumulated time becomes greater or equal, a pulse is generated (it will operate only one cycle), we subtract 1000 from it (suddenly we get a little more than 1000, therefore it is impossible to zero) and for one controller cycle we have a positive pulse.

From these pulses it is very easy to add large quantities, for example, 5 seconds.

You can also minutes, hours, but that's another story.

**Please note that you cannot use the same front variable twice, this will give a very difficult to catch error in the program logic.**Now it's the turn of the functional block of the snake itself.

Input variables are left, right, up, down, and start commands.

If you have a controller and a discrete input module at hand, you can hang inputs on these variable inputs, to which buttons are attached without fixing. Get a full-fledged slot machine. With special desire, an array can also be made of light bulbs, but I’ll be fired immediately for this =)

The first example is a left move command.

If we filed it, we don’t move to the right, it happened only in this cycle, then we reset all previous commands and announce the movement to the left.

**Next, when the game starts, we free the array**

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

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

It is freed by simply filling in zeros from 0 to 99th element. The fact is that in STL there is no work with two-dimensional arrays with indirect addressing, so we will represent this array as a one-dimensional element from 0 to 99th element.

At the start, we transfer the snake’s head in direct addressing to element 9.5, make it 2 length, give the command to crawl up, reset the game over and give the command to throw food out to a random point on the playing field.

**game start**

A # snake.start

FP # frnts.pos6

JCN strt

LP # 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

FP # frnts.pos6

JCN strt

LP # 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

Next we need to generate food for the snake. I confess that the generation algorithm itself was spied on me at one of Google’s first links, on the PLC for Good website.

It consists in the fact that the controller counts milliseconds of system time. If we take this number, add it to a random one, and then discard the excess, we get a pseudo-random number generator from 0 to a given value.

Further, when X and Y fell out randomly, we take them and calculate the number of the array element. Each step along X means that you need to move to the next row of elements, that is, 10, and each along Y means moving from the 0-element to 1.

As a result, the element of the array X [4] Y [7] turns into the 47th element of the one-dimensional array . We give him status 7 - food.

In case the element is busy, we start the generator again.

**food generator**

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

LP # 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

LP # 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

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

LP # 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

LP # 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

After a successful ejection of food, the snake begins its movement, consider an algorithm based on left movement.

**move left**

A “db_pulsegen” .two_sec_pls

A # move.left

JCN ext1

OPN “massive”

LAR1 #coordinate

TAR1

LP # 10.0

MOD

LP # 0.0

== D

JCN ok_1

S # snake.gameover

JU gmov

ok_1: TAR1

LP # 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

A # move.left

JCN ext1

OPN “massive”

LAR1 #coordinate

TAR1

LP # 10.0

MOD

LP # 0.0

== D

JCN ok_1

S # snake.gameover

JU gmov

ok_1: TAR1

LP # 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

In this algorithm, we immediately perform several checks. Divide by 10 - we get the remainder - the number in the line of the current element. If we move to the left, being in the zero element - the end of the game.

The same thing happens if there is anything other than food in the way of movement. If you stumble upon food - cock the bit that you just ate, it will start the generator, extend the tail by 1.

Next is the last algorithm - “Tail Cutter”. He takes the last coordinate as a basis, reads the command at this coordinate and goes in the reverse order from the head of the snake to the tail. If the cell goes beyond the length - delete it by zeroing the value

**cut tails**

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_coord

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

LP # 1.0

+ D

T # tail_cut.tmp_coordinate

L #looper

L 1

+ I

T #looper

JU lpct

m_rt: L # tail_cut.tmp_coordinate

LP # 1.0

-D

T # tail_cut.tmp_coordinate

L #looper

L 1

+ I

T #looper

JU lpct

m_up: L # tail_cut.tmp_coordinate

LP # 10.0

+ D

T # tail_cut.tmp_coordinate

L #looper

L 1

+ I

T #looper

JU lpct

m_dn: L # tail_cut.tmp_coordinate

LP # 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

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_coord

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

LP # 1.0

+ D

T # tail_cut.tmp_coordinate

L #looper

L 1

+ I

T #looper

JU lpct

m_rt: L # tail_cut.tmp_coordinate

LP # 1.0

-D

T # tail_cut.tmp_coordinate

L #looper

L 1

+ I

T #looper

JU lpct

m_up: L # tail_cut.tmp_coordinate

LP # 10.0

+ D

T # tail_cut.tmp_coordinate

L #looper

L 1

+ I

T #looper

JU lpct

m_dn: L # tail_cut.tmp_coordinate

LP # 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

Thank you very much for your attention to everyone who reached the end of the article, I hope it was interesting.

Link to download the project