#PLC_用 PLC 玩贪食蛇,无尘室机台中的彩蛋(含代码解析)

无尘室机台中的小游戏 1.贪食蛇架构a.贪食蛇元素b.贪食蛇控制c.贪食蛇逻辑 2.建立框架a.地图b.食物c.蛇d.建立输入讯号e.程序变量 3.结合控制逻

无尘室机台中的小游戏

  • 1.贪食蛇架构
    • a.贪食蛇元素
    • b.贪食蛇控制
    • c.贪食蛇逻辑
  • 2.建立框架
    • a.地图
    • b.食物
    • c.蛇
    • d.建立输入讯号
    • e.程序变量
  • 3.结合控制逻辑
    • a.画出蛇
    • b.画出食物
    • c.得分
  • 3.代码

去年有幸能够主导前东家在江苏某 X 果手机代工厂内的自动化机台控制,机台调适顺利后在现场待了几天进行陪产,由于无尘室车间实在太无聊了,于是决定在程序中植入一个贪食蛇的小彩蛋。

先上动图,演示在触摸屏上玩贪食蛇
请添加图片描述

1.贪食蛇架构

a.贪食蛇元素

构成贪食蛇游戏主要有以下几点

  1. 蛇头
  2. 蛇身
  3. 食物
  4. 墙壁

b.贪食蛇控制

贪食蛇控制以上、下、左、右四种输入为主,其中贪食蛇往某一方向行走时:

  • 按下对应的方向或对应的反向是不动做的
  • 按下对应方向的相邻两方向,则改变行进方向

c.贪食蛇逻辑

  • 蛇头牵着蛇身运动
  • 蛇头与食物位置发生重叠,食物消失并随机产生新食物,以及分数累加、蛇身加长
  • 蛇头与食物之外的物体发生重叠,则结束游戏

有了以上几点后我们可以开始建立程序

2.建立框架

a.地图

首先我们需要建立一张地图供贪食蛇本身以及食物摆放,由于采用指示灯阵列建立起点阵,为了让触摸屏的布局较为简单所以 x x x 座标存放在低位, y y y 座标则存放在高位。
也就是说一个座标点 P o s Pos Pos 的表示为:
P o s = x + y < < 4 Pos=x+y<<4 Pos=x+y<<4

因此 16 × 16 16\times 16 16×16 地图定义如下,EMPTY 为用来清除地图画面的 FALSE 集合。

	MAP : ARRAY[0..255] OF BOOL;EMPTY : ARRAY[0..255] OF BOOL;

b.食物

由于食物的座标是随机计算出来的,因次我们套用上述的座标公式后,只要产生一个和地图数组一样长度的随机值,即可拆分成 x x x y y y 座标:

	Food_Index : INT;Food_Pos : INT;

c.蛇

蛇的构成和地图不同

蛇的数组是用来储存一串蛇的座标

并且会随长度的增加,增加占用的长度
随着位置的移动,改变储存的座标

所以这里我们还是建立一个和地图同样长度的数组(考量到可能有高手真的会玩到全满):

	Snake : ARRAY[0..255] OF UDINT;

d.建立输入讯号

为了方便调用程序,这里将上下左右以索引表示,因此建立一个布尔量的数组:

	//Right: 0//Down: 1//Left: 2//Up: 3Direction : ARRAY[0..3] OF BOOL;

e.程序变量

由于 PLC 和 C 之间的差异,PLC 必须在程序运行前确认好所需要的记忆体,不能建立暂时变量,因此在开头我们都需要先定义所用到的程序变量:

	//流程Seq: INT;//失败的旗标Faild: BOOL;//当前等级得分Level: INT;Score: INT;//升级计数器,吃到多少次食物后进行升级Level_Up_Count: INT;//除频使用的计数器,由于 PLC 是以固定周期执行程序,透过除频调整蛇的速度Count: INT;//产生随机变数的功能块Ran: Rand;//随机数的范围RanRange: REAL;//蛇的长度Length: INT;//用来储存当前蛇的运行方向Direction_State: INT;//蛇的座标x: UINT;y: UINT;//蛇在移动过程的暂存ToNex: UINT;FromP: UINT;//画蛇的For回圈计数值index: UINT;

3.结合控制逻辑

到这一步之基本上已经解决 70% 内容,现在主要的工作是如何将蛇与食物画在地图上,并且更新蛇的移动。

a.画出蛇

这边我们给定蛇的初始位置、初始长度与初始方向:

	Length := 3;Direction_State := 0;x := 5;y := 7;Snake[0] := 5 * 256 + 7;Snake[1] := 4 * 256 + 7;Snake[2] := 3 * 256 + 7;

蛇的位移方向:

	IF Direction[0] AND Direction_State<>2 THENDirection_State := 0;END_IF;IF Direction[1] AND Direction_State<>3 THENDirection_State := 1;END_IF;IF Direction[2] AND Direction_State<>0 THENDirection_State := 2;END_IF;IF Direction[3] AND Direction_State<>1 THENDirection_State := 3;END_IF;

蛇的移动需要考量到有没有超过地图的边界,若超过则游戏结束,没有则正常移动:

CASE Direction_State OF 
0:IF x>=15 THENFaild := TRUE;ELSEx := x + 1;END_IF;
;
1:IF y<=0 THENFaild := TRUE;ELSEy := y - 1;END_IF;
;
2:IF x<=0 THENFaild := TRUE;ELSEx := x - 1;END_IF;
;
3:IF y>=15 THENFaild := TRUE;ELSEy := y + 1;END_IF;
;
END_CASE;

蛇座标的更新,需要考量到蛇头的新座标,以及将旧座标传递到下一个数列,

FOR index := 0 TO (Length - 1) DOIF index = 0 THENToNext := Snake[index];Snake[index] := x * 256 + y;ELSEFromP := ToNext;ToNext := Snake[index];Snake[index] := FromP;END_IF;
END_FOR;

将蛇画在地图中:

//绘图前先将画面清空
MAP := EMPTY;FOR index := 0 TO (Length-1) DOMAP[ Snake[index] / 256 + (Snake[index] MOD 256) * 16] := TRUE;
END_FOR

b.画出食物

接着是如何产生食物,由于 PLC 的运行中是需要尽量减少 CPU 的计算时长,为了避免以 while 回圈计算食物的座标不会和蛇的座标重叠

采用地图能用的空间中随机产生食物,换句话说食物的出现需要考量到当前地图剩余的空间,以及不能和蛇的位置重叠。

这里我们采用产生随机数值的指令 RDM (各家 PLC 的指令不一定相同),此指令会生成 0 ~ 1 之间的随机数值,再乘以剩余空间的总量后取整数,就可以产生食物座标的序列:

RanRange := 256 - Length;
Food_Index := REAL_TO_INT( Ran() * RanRange );

接着将 Food_Index 扫描整张地图,跳过蛇的座标后即是食物的座标:

FOR index := 0 TO Food_Index. DOIF MAP[index] THENindex := index - 1;END_IFFood_Pos := Food_Pos + 1;
END_FORMAP[Food_Pos] := TRUE;

c.得分

得分的判断只需要判断当前的蛇头座标是否和食物座标重叠即可,并以 5 个食物为单位提升蛇的移动速度和分数计算:

IF Food_Pos = Snake[0] / 256 + (Snake[0] MOD 256) * 16 THENLevel_Up_Count := (Level_Up_Count + 1);Score := Score + LEVEL;IF Level_Up_Count = 5 AND LEVEL < 20 THENLEVEL := LEVEL + 1;Level_Up_Count := 0;END_IF;Length := Length+1;
END_IF

3.代码

下面将启动、结束以及蛇和食物画在地图上的功能进行整合:

CASE SEQ OF0://Wait start signalIF START THENMAP := EMPTY;LEVEL := 1;Score := 0;Level_Up_Count := 0;Length := 3;DIRECTION_STATE := 0;R.Execute := TRUE;x := 5;y := 7;SA[0] := 5 * 256 + 7;SA[1] := 4 * 256 + 7;SA[2] := 3 * 256 + 7;START := FALSE;SEQ := 1;END_IF;1:IF Count=0 THENCASE DIRECTION_STATE OF 0:IF x>=15 THENFAILED := TRUE;ELSEx := x + 1;END_IF;;1:IF y<=0 THENFAILED := TRUE;ELSEy := y - 1;END_IF;;2:IF x<=0 THENFAILED := TRUE;ELSEx := x - 1;END_IF;;3:IF y>=15 THENFAILED := TRUE;ELSEy := y + 1;END_IF;;END_CASE;IF MAP[x+y*16] THENIF Food_Pos = Snake[0] / 256 + (Snake[0] MOD 256) * 16 THENScore := Score + LEVEL;Level_Up_Count := ( Level_Up_Count + 1 );IF Level_Up_Count = 5 AND LEVEL < 20 THENLEVEL := LEVEL + 1;Level_Up_Count := 0;END_IF;Length := Length+1;ELSEFAILED := TRUE;END_IF;END_IF;IF FAILED THENMAP := EMPTY;LEVEL := 0;Score := 0;SEQ := 2;ELSE//MovingFOR MOVING:=0 TO Length-1 BY 1 DOIF MOVING=0 THENToNext := SA[MOVING];SA[MOVING] := x * 256 + y;ELSEFromP := ToNext;ToNext := SA[MOVING];SA[MOVING] := FromP;END_IF;END_FOR;//PaintingMAP := EMPTY;MAP[Food_Pos] := TRUE;FOR PaintCount:=0 TO Length-1 DOMAP[ SA[PaintCount] / 256 + (SA[PaintCount] MOD 256) * 16] := TRUE;END_FOR;END_IF;END_IF;Count := (Count + 1) MOD (20/LEVEL);2:IF START THENSTART := FALSE;FAILED := FALSE;SEQ := 0;END_IF;
END_CASE;