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

无尘室机台中的小游戏

  • 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: 3
	Direction : 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 THEN
		Direction_State := 0;
	END_IF;
	IF Direction[1] AND Direction_State<>3 THEN
		Direction_State := 1;
	END_IF;
	IF Direction[2] AND Direction_State<>0 THEN
		Direction_State := 2;
	END_IF;
	IF Direction[3] AND Direction_State<>1 THEN
		Direction_State := 3;
	END_IF;

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

CASE Direction_State OF 
0:
	IF x>=15 THEN
		Faild := TRUE;
	ELSE
		x := x + 1;
	END_IF;
;
1:
	IF y<=0 THEN
		Faild := TRUE;
	ELSE
		y := y - 1;
	END_IF;
;
2:
	IF x<=0 THEN
		Faild := TRUE;
	ELSE
		x := x - 1;
	END_IF;
;
3:
	IF y>=15 THEN
		Faild := TRUE;
	ELSE
		y := y + 1;
	END_IF;
;
END_CASE;

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

FOR index := 0 TO (Length - 1) DO
   IF index = 0 THEN
   		ToNext := Snake[index];
   		Snake[index] := x * 256 + y;
   	ELSE
		FromP := ToNext;
		ToNext := Snake[index];
		Snake[index] := FromP;
   	END_IF;
END_FOR;

将蛇画在地图中:

//绘图前先将画面清空
MAP := EMPTY;

FOR index := 0 TO (Length-1) DO
	MAP[ 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. DO
	IF MAP[index] THEN
		index := index - 1;
	END_IF
	Food_Pos := Food_Pos + 1;
END_FOR

MAP[Food_Pos] := TRUE;

c.得分

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

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

3.代码

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

CASE SEQ OF
	0:
		//Wait start signal
		IF START THEN
			MAP := 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 THEN
			CASE DIRECTION_STATE OF 
				0:
					IF x>=15 THEN
						FAILED := TRUE;
					ELSE
						x := x + 1;
					END_IF;
				;
				1:
					IF y<=0 THEN
						FAILED := TRUE;
					ELSE
						y := y - 1;
					END_IF;
				;
				2:
					IF x<=0 THEN
						FAILED := TRUE;
					ELSE
						x := x - 1;
					END_IF;
				;
				3:
					IF y>=15 THEN
						FAILED := TRUE;
					ELSE
						y := y + 1;
					END_IF;
				;
			END_CASE;
			
			IF MAP[x+y*16] THEN
				IF Food_Pos = Snake[0] / 256 + (Snake[0] MOD 256) * 16 THEN
					Score := Score + LEVEL;
					Level_Up_Count := ( Level_Up_Count + 1 );
					IF Level_Up_Count = 5 AND LEVEL < 20 THEN
						LEVEL := LEVEL + 1;
						Level_Up_Count := 0;
					END_IF;
					Length := Length+1;
				ELSE
					FAILED := TRUE;
				END_IF;
			END_IF;
			
			IF FAILED THEN
				MAP := EMPTY;
				LEVEL := 0;
				Score := 0;
				SEQ := 2;
			ELSE
				//Moving
				FOR MOVING:=0 TO Length-1 BY 1 DO
				   	IF MOVING=0 THEN
				   		ToNext := SA[MOVING];
				   		SA[MOVING] := x * 256 + y;
				   	ELSE
						FromP := ToNext;
						ToNext := SA[MOVING];
						SA[MOVING] := FromP;
				   	END_IF;
				END_FOR;
				//Painting
				MAP := EMPTY;
				MAP[Food_Pos] := TRUE;
				FOR PaintCount:=0 TO Length-1 DO
					MAP[ SA[PaintCount] / 256 + (SA[PaintCount] MOD 256) * 16] := TRUE;
				END_FOR;
			END_IF;
		END_IF;
		Count := (Count + 1) MOD (20/LEVEL);
		
	2:
		IF START THEN
			START := FALSE;
			FAILED := FALSE;
			SEQ := 0;
		END_IF;
END_CASE;

你可能感兴趣的:(PLC,编程语言)