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


去年有幸能够主导前东家在江苏某 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;
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
贪食蛇(Snake)是一种经典的游戏家通过控制蛇的移动来吃食物,蛇的长度会不断增长。下面是一个由Python写的贪食蛇代码示例: ```python import pygame import random WIDTH, HEIGHT = 640, 480 GRID_SIZE = 20 GRID_WIDTH = WIDTH // GRID_SIZE GRID_HEIGHT = HEIGHT // GRID_SIZE UP = (0, -1) DOWN = (0, 1) LEFT = (-1, 0) RIGHT = (1, 0) def draw_snake(snake): for block in snake: rect = pygame.Rect(block[0] * GRID_SIZE, block[1] * GRID_SIZE, GRID_SIZE, GRID_SIZE) pygame.draw.rect(screen, (0, 255, 0), rect) def move_snake(snake, direction): head = (snake[0][0] + direction[0], snake[0][1] + direction[1]) snake.insert(0, head) snake.pop() def generate_food(): while True: x = random.randint(0, GRID_WIDTH - 1) y = random.randint(0, GRID_HEIGHT - 1) if (x, y) not in snake: return (x, y) pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) clock = pygame.time.Clock() snake = [(GRID_WIDTH // 2, GRID_HEIGHT // 2)] direction = RIGHT food = generate_food() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_w and direction != DOWN: direction = UP elif event.key == pygame.K_s and direction != UP: direction = DOWN elif event.key == pygame.K_a and direction != RIGHT: direction = LEFT elif event.key == pygame.K_d and direction != LEFT: direction = RIGHT move_snake(snake, direction) if snake[0] == food: food = generate_food() else: snake.pop() screen.fill((0, 0, 0)) draw_snake(snake) pygame.draw.rect(screen, (255, 0, 0), pygame.Rect(food[0] * GRID_SIZE, food[1] * GRID_SIZE, GRID_SIZE, GRID_SIZE)) pygame.display.flip() clock.tick(10) pygame.quit() ``` 这段代码使用了pygame库来实现游戏的绘图与事件处理部分,snake列表存储贪食蛇的身体块的坐标,direction表示蛇的移动方向。通过键盘事件来控制蛇的移动方向,每帧更新蛇的位置并绘制在屏幕上,同时检测蛇是否吃到食物并更新食物的位置。游戏结束后调用`pygame.quit()`来退出pygame。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值