先上动图,演示在触摸屏上玩贪食蛇
构成贪食蛇游戏主要有以下几点
贪食蛇控制以上、下、左、右四种输入为主,其中贪食蛇往某一方向行走时:
有了以上几点后我们可以开始建立程序
首先我们需要建立一张地图供贪食蛇本身以及食物摆放,由于采用指示灯阵列建立起点阵,为了让触摸屏的布局较为简单所以 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;
由于食物的座标是随机计算出来的,因次我们套用上述的座标公式后,只要产生一个和地图数组一样长度的随机值,即可拆分成 x x x y y y 座标:
Food_Index : INT;
Food_Pos : INT;
蛇的构成和地图不同
蛇的数组是用来储存一串蛇的座标
并且会随长度的增加,增加占用的长度
随着位置的移动,改变储存的座标
所以这里我们还是建立一个和地图同样长度的数组(考量到可能有高手真的会玩到全满):
Snake : ARRAY[0..255] OF UDINT;
为了方便调用程序,这里将上下左右以索引表示,因此建立一个布尔量的数组:
//Right: 0
//Down: 1
//Left: 2
//Up: 3
Direction : ARRAY[0..3] OF BOOL;
由于 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;
到这一步之基本上已经解决 70% 内容,现在主要的工作是如何将蛇与食物画在地图上,并且更新蛇的移动。
这边我们给定蛇的初始位置、初始长度与初始方向:
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
接着是如何产生食物,由于 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;
得分的判断只需要判断当前的蛇头座标是否和食物座标重叠即可,并以 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
下面将启动、结束以及蛇和食物画在地图上的功能进行整合:
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;