# Collaborated with AI to develop a Snake game

**URL:** <https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592>\
**Category:** Shared Projects\
**Created:** [May 26, 2026, 5:34am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592 "2026-05-26T05:34:35Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![bob.chen](https://avatars.discourse-cdn.com/v4/letter/b/a6a055/32.png) [@bob.chen](https://community.factoryio.com/u/bob.chen)\
**Post date:** [May 26, 2026, 5:34am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/1 "2026-05-26T05:34:35Z")

</div>

Hello everyone,

Using the LED panel matrix scene shared by Amjavi6 as inspiration, I created a secondary development project by expanding it into a 16×16 matrix. I then collaborated with AI to develop a Snake game on top of it.

Software used:

1. Factory I/O
2. TIA Portal
3. PLCSIM Advanced

PLC programming language:

- SCL (Structured Control Language)

[貪食蛇.factoryio](https://community.factoryio.com/uploads/short-url/ad8qY6ZzIB9RuLKv5tkA9sumpav.factoryio) (431.2 KB)

 ![image](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/2/291efad6e65da7782f5a0e20cdfc0a84d9765ad6.jpeg)

---

<div class="post-metadata">

**Author:** ![brunovgr](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/brunovgr/32/12_2.png) [@brunovgr](https://community.factoryio.com/u/brunovgr)\
**Post date:** [May 26, 2026, 8:00am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/2 "2026-05-26T08:00:33Z")

</div>

@bob.chen Super cool project 😉

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 26, 2026, 10:16am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/3 "2026-05-26T10:16:54Z")

</div>

Hi Bob Chen:

Now, when what you mentioned is working, post the video here so I can see it. Among other things, I highly doubt that project can be done with SCL, and it must be very complex to do it with only the 136 LEDs on your panel. It’s much easier for it to solve a 6x6 Sudoku, or even make the moves in a chess game. I’m looking forward to your video of the snake working…

I should mention that adapting that game to a [Factory.io](http://Factory.io) scene doesn’t seem like the best thing to suggest to students, in my opinion, but don’t take my word for it; stranger things have happened.

[![](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/2/2a2de84e51b427c2b8a048c0778d17ad86d7bc5d.jpeg "JUEGO SNAKE P 2") ](https://www.youtube.com/watch?v=vlOWVLwAEXM)

Regards

---

<div class="post-metadata">

**Author:** ![bob.chen](https://avatars.discourse-cdn.com/v4/letter/b/a6a055/32.png) [@bob.chen](https://community.factoryio.com/u/bob.chen)\
**Post date:** [May 26, 2026, 12:04pm UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/4 "2026-05-26T12:04:47Z")

</div>

Hi, Amjavi6,

I cannot upload the SCL file directly here, so please share your email address and I can send you the original source file.

This project was developed through human-AI collaboration. The main point is not the Snake game itself, but rather using AI collaboration to first develop working code, and then reverse-learn the problem-solving process from that working example. This allows students to quickly acquire more skills by learning from executable code.

That is also why I shared my development process with ChatGPT. You can clearly see the interaction between me and the AI throughout the entire workflow. Through this interaction, I believe PLC engineers in the AI era should improve their Prompt skills.

Therefore, my concept is that PLC is evolving from Programming Logic Control into Prompt Logic Control.

In the past, experts were created through 10,000 hours of practice.  
In the future, experts may be created through 10,000 iterations.

[貪食蛇.factoryio](https://community.factoryio.com/uploads/short-url/ad8qY6ZzIB9RuLKv5tkA9sumpav.factoryio) (431.2 KB)

[![](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/f/ffff0e4727e86d7b1bc38c3b09bcdafd97a6b525.jpeg "從 0 到完成：AI 協助產生可執行的 PLC SCL 貪食蛇程式") ](https://www.youtube.com/watch?v=4flSClNZuYs)

> **[看看這段對話](https://chatgpt.com/share/6a145557-9b80-83a2-b9ad-e99423848d08)**
>
> 有人覺得你會想看看這段對話。

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 26, 2026, 6:22pm UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/5 "2026-05-26T18:22:39Z")

</div>

Hi Bob Chen:

Your presentation of the Snake game is very good, but pay attention to what I’m about to say:

First of all, I don’t use Siemens, so I can’t see your files.

Second of all, it’s obvious that your project isn’t finished. Right now, it’s just doing a short cycle of certain lights on your LEDs. Obviously, if the snake finishes, the board has to start from the other side, and we also don’t know if at that speed it would have enough time to scan all the bits and indicators without crashing due to the high scan cycle.

Since you’ve shown interest in the topic, and as you can see, I did this 5 years ago on my channel, what I’m going to do is ADAPT what I already did to a PLC so that your board works. BUT, for now, I’m going to do it in a very simple way, very simple, so that you understand how to make that game in FACTORY IO.

Thirdly, when you see what I’m going to do, you’ll understand that SCL can’t do what I’m going to show you with [VB.NET](http://VB.NET). A typical student can’t do it either, but that doesn’t matter to us. Since it’s already happened, we’re going to see it through to the end.

This means that if I manage to create the Snake game in [Factory.io](http://Factory.io), explaining the process here in a simple and understandable way for students, we can say that [Factory.io](http://Factory.io), as it is, is much more of a simulator than I thought.

Bob Chen, I’m taking over your project. I hope you can do the same as me, since the idea was yours…

Thanks for contributing such an interesting topic.

Regards

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 26, 2026, 7:14pm UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/6 "2026-05-26T19:14:47Z")

</div>

Hi Bob Chen:

[![](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/1/1918639f5d84928312a8622c3f48816c53da72c1.jpeg "SNAKE PLC") ](https://www.youtube.com/watch?v=OHeIw6b25OA)

As I mentioned, I don’t know if it will work. I usually test it before uploading the project, but this time, let’s see how the idea works, and when I finish contribution 127, we’ll start…

Let’s see what the creators of Factory IO, Bruno, Adriana, etc., think. Did they think their simulator was suitable for gaming?

It has solved Sudokus, played Connect 4, played chess, built the German Enigma machine, and now the Snake game.

I repeat, I don’t know if the Snake scan cycle will be fast enough for so many bits, but hey, we’re here to play, literally.

Regards

---

<div class="post-metadata">

**Author:** ![bob.chen](https://avatars.discourse-cdn.com/v4/letter/b/a6a055/32.png) [@bob.chen](https://community.factoryio.com/u/bob.chen)\
**Post date:** [May 27, 2026, 12:52am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/7 "2026-05-27T00:52:04Z")

</div>

# What Students Can Learn from a PLC Snake Game in Siemens SCL

This project is more than a small PLC game. It is a practical teaching case that shows how students can move beyond basic input-output control and start thinking in terms of states, data, timing, hardware mapping, and verification.  
The goal is to use Siemens SCL to control a 16x16 LED matrix and build a working Snake Game. During this process, students learn several problem-solving ideas that are not easy to discover in traditional PLC lessons.

## 1. Separate Logic from Hardware Mapping

A key lesson is that the game logic should not be directly tied to physical output addresses.  
Inside the program, the snake uses a simple coordinate system:

```plaintext
X = 0 to 15
Y = 0 to 15
X = 0 means left
X = 15 means right
Y = 0 means top
Y = 15 means bottom

```

However, the real LED matrix is controlled by Siemens output words, bytes, and bits. For example, the first row is mapped from:

```plaintext
Q0.0, Q0.1, Q0.2, Q0.3, Q0.4, Q0.5, Q0.6, Q0.7,
Q1.0, Q1.1, Q1.2, Q1.3, Q1.4, Q1.5, Q1.6, Q1.7

```

Therefore, the program needs a mapping layer:

```scl
IF #SnakeX[#i] <= 7 THEN
    #BitIndex := #SnakeX[#i] + 8;
ELSE
    #BitIndex := #SnakeX[#i] - 8;
END_IF;

```

The important engineering idea is:  
**The logical model and the physical I/O mapping should be separated.**  
This is useful not only for LED matrices, but also for Modbus registers, PROFINET I/O mapping, HMI tags, SCADA tags, and byte-order problems.

## 2. Use a Layered Program Structure

The Snake Game can be divided into three layers:

```plaintext
1. Game logic
   Direction, snake body, food, collision, score
2. Timing control
   GameTimer, GameTick, MoveTime
3. Display output
   LedBuffer, BitIndex, LedRow_00 to LedRow_15

```

This teaches students not to mix all conditions and outputs together.  
A good design flow is:

```plaintext
Read input
-> update direction
-> wait for GameTick
-> calculate next position
-> check collision
-> update snake body
-> generate LED buffer
-> write LED output

```

This is a major step beyond simple PLC logic such as:

```plaintext
Condition is true -> Output ON
Condition is false -> Output OFF

```

## 3. Use a State Machine Instead of Only Boolean Logic

The game has several operating states:

```plaintext
Idle
Running
Game Over
Win
Restart

```

Therefore, the program uses a CASE-based state machine:

```scl
CASE #State OF
    10:
        // Idle
    20:
        // Running
    30:
        // Game Over
    40:
        // Win
END_CASE;

```

This teaches students that Boolean logic is not always enough. When a machine has steps, memory, events, and transitions, a state machine is usually a better solution.  
This concept applies directly to conveyors, sorting systems, pick-and-place machines, automatic warehouses, packaging machines, and batch processes.

## 4. Understand Scan Cycle vs. Application Event

A PLC runs cyclically, but the snake should not move every scan. If it did, it would move too fast to observe.  
The program uses a timer to generate a movement event:

```scl
#GameTimer(IN := (#State = 20), PT := #MoveTime);
#GameTick := #GameTimer.Q;

```

The snake only moves when `GameTick` becomes TRUE:

```scl
IF #GameTick THEN
    // Move snake
END_IF;

```

The key point is:  
**The PLC scan cycle is not the same as the application control cycle.**  
This idea is useful for communication polling, periodic sampling, HMI refresh, machine cycle timing, and step-by-step process control.

## 5. Use Arrays to Represent Moving Objects

The snake body is not written as hundreds of individual Boolean tags. It is stored as arrays:

```scl
SnakeX : Array[0..255] of Int;
SnakeY : Array[0..255] of Int;

```

The structure is:

```plaintext
SnakeX[0], SnakeY[0] = snake head
SnakeX[1], SnakeY[1] = first body segment
SnakeX[2], SnakeY[2] = second body segment

```

This teaches students how to use data structures in PLC programming.  
The lesson is:  
**When many similar data items exist, use arrays and indexes.**  
This idea is very useful for product tracking, conveyor station data, recipe tables, alarm lists, warehouse locations, and production history.

## 6. Learn Shift Register Thinking

The snake body movement is based on a simple but powerful algorithm:

```scl
FOR #i := #BodyLimit TO 1 BY -1 DO
    #SnakeX[#i] := #SnakeX[#i - 1];
    #SnakeY[#i] := #SnakeY[#i - 1];
END_FOR;

```

Each body segment copies the previous segment position. Only the snake head actually moves according to the direction.  
This is similar to shift-register logic in industrial automation.  
It can be applied to:

```plaintext
Object tracking on conveyors
Station-to-station data transfer
Product status shifting
Inspection result tracking
Packaging line data movement
FIFO-style material tracking

```

## 7. Calculate the Next State Before Updating the Real State

The program does not immediately overwrite the snake position. It first calculates the next possible position:

```scl
#NewX := #SnakeX[0];
#NewY := #SnakeY[0];

```

Then it checks whether the new position is valid. Only after validation does the program update the real snake position.  
The engineering principle is:  
**Calculate first, validate second, commit last.**  
This is useful for motion command validation, robot position checking, safety interlocks, recipe validation, and automatic sequence transitions.

## 8. Learn Bit Operations with SHL and OR

The LED matrix is generated using bit operations:

```scl
SHL(IN := WORD#16#0001, N := #BitIndex)

```

Then the bit is added into the row buffer:

```scl
#LedBuffer[#SnakeY[#i]] :=
    #LedBuffer[#SnakeY[#i]] OR SHL(IN := WORD#16#0001, N := #BitIndex);

```

This teaches students that a WORD can represent 16 LED points.  
Instead of writing 16 separate Boolean outputs, the program uses:

```plaintext
WORD as a 16-bit row
SHL to generate a bit mask
OR to combine display points
LedBuffer to prepare output before writing to hardware

```

This is a strong example of why SCL is suitable for data-oriented PLC problems.

## 9. Use MOD for Position Generation

The food position is generated with a simple pseudo-random method:

```scl
#FoodX := #Seed MOD 16;
#FoodY := (#Seed / 16) MOD 16;

```

`MOD 16` keeps the value inside the range 0 to 15.  
This teaches students how to convert a changing number into a valid matrix position.  
The same idea can be used for station indexing, recipe selection, device polling, buffer addressing, and circular queue logic.

## 10. Treat Collision Detection as Illegal State Checking

The program checks wall collision:

```scl
IF (#NewX < 0) OR (#NewX > 15) OR (#NewY < 0) OR (#NewY > 15) THEN
    #GameOver := TRUE;
END_IF;

```

It also checks self-collision by comparing the new head position with the snake body:

```scl
FOR #i := 0 TO #BodyLimit DO
    IF (#NewX = #SnakeX[#i]) AND (#NewY = #SnakeY[#i]) THEN
        #SelfCollision := TRUE;
    END_IF;
END_FOR;

```

This teaches students how to detect illegal states before updating the machine state.  
In real automation, this maps to position limits, duplicate station occupancy, invalid routing, robot conflict checking, and conveyor zone conflict detection.

## 11. Reset Must Prevent Later Logic from Rewriting Outputs

During development, one issue appeared: Reset cleared the LED buffer, but later logic drew the snake again in the same scan.  
The solution was:

```scl
IF NOT #Reset THEN
    // Draw food and snake
END_IF;

```

The key lesson is:  
**Reset logic must not only clear values. It must also prevent later logic from writing them again.**  
This is a common real-world PLC issue in emergency stop logic, alarm reset logic, manual-auto switching, output arbitration, and machine initialization.

## 12. Use Watch Table to Verify the Causal Chain

The Watch Table should not only check whether the LED turns on. It should verify the full causal chain:

```plaintext
DI button
-> NextDirection
-> GameTick
-> Direction
-> HeadX / HeadY
-> SnakeX / SnakeY
-> LedBuffer
-> LedRow output
-> physical LED display

```

Recommended Watch Table variables:

```plaintext
State
Direction
NextDirection
HeadX
HeadY
FoodX
FoodY
SnakeLength
Score
GameTimer.ET
GameTick
GameOver
Win
LedRow_00 to LedRow_15

```

This teaches students how to debug by tracing cause and effect, not just by looking at the final output.

## 13. SCL Solves a Different Type of Problem Than Ladder

Ladder is excellent for:

```plaintext
Start/stop circuits
Interlocks
Simple output logic
Safety contact logic
Basic machine conditions

```

But this project requires:

```plaintext
Arrays
Indexes
Loops
State machines
Bit shifting
Data movement
Collision checking
Coordinate mapping

```

These are easier to express in SCL.  
The important message is:  
**SCL is not better because it is newer. It is better when the problem is data-oriented and algorithmic.**

## 14. The Development Process Is More Valuable Than the Final Code

The most important learning point is the process:

```plaintext
Start with a simple idea
-> build a minimal version
-> test with Watch Table
-> find mapping errors
-> fix Reset behavior
-> verify movement
-> add snake body
-> add food and score
-> fix syntax errors
-> complete the final version

```

This teaches students that engineering is not about writing perfect code in one attempt.  
Engineering is about:

```plaintext
Defining assumptions
Testing assumptions
Using evidence
Separating logic layers
Fixing one problem at a time
Protecting verified functions
Upgrading gradually

```

## Summary

This Snake Game SCL project teaches much more than a game.  
It helps students learn:

```plaintext
State machine thinking
Coordinate modeling
Array-based data structures
Timer-driven events
Bit-level output mapping
Collision detection
Data buffering
Watch Table diagnostics
Step-by-step engineering validation

```

These skills are not always visible in traditional PLC examples such as motor start-stop circuits, self-holding circuits, or traffic lights.  
The core message is:  
**PLC programming is not only about turning outputs ON and OFF. It is about modeling states, data, timing, hardware mapping, and verification.**  
That is why this Snake Game is a strong teaching case for modern PLC education, especially when combined with Siemens SCL, Factory I/O, and AI-assisted development.

---

<div class="post-metadata">

**Author:** ![bob.chen](https://avatars.discourse-cdn.com/v4/letter/b/a6a055/32.png) [@bob.chen](https://community.factoryio.com/u/bob.chen)\
**Post date:** [May 27, 2026, 2:01am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/8 "2026-05-27T02:01:37Z")

</div>

Hi, Amjavi6

Below is the complete SCL source code. Since the essence of SCL is simply a text-based file format, the code can be directly copied into a Notepad TXT file. Then, you only need to change the file extension from `.txt` to `.scl`.

For example:  
`xxx.txt` → manually rename to `xxx.scl`

After that, the `.scl` file can be imported directly into Siemens TIA Portal.

This also allows you to clearly see how SCL is used to implement the entire Snake game control logic, while learning the underlying problem-solving methodology behind the program.

FUNCTION\_BLOCK “FB\_SnakeGame\_16x16”  
{ S7\_Optimized\_Access := ‘TRUE’ }  
VERSION : 0.5

VAR\_INPUT  
Start : Bool;  
Reset : Bool;  
Pause : Bool;  
BtnUp : Bool;  
BtnDown : Bool;  
BtnLeft : Bool;  
BtnRight : Bool;  
MoveTime : Time := T#300ms;  
END\_VAR

VAR\_OUTPUT  
LedRow\_00 : Word;  
LedRow\_01 : Word;  
LedRow\_02 : Word;  
LedRow\_03 : Word;  
LedRow\_04 : Word;  
LedRow\_05 : Word;  
LedRow\_06 : Word;  
LedRow\_07 : Word;  
LedRow\_08 : Word;  
LedRow\_09 : Word;  
LedRow\_10 : Word;  
LedRow\_11 : Word;  
LedRow\_12 : Word;  
LedRow\_13 : Word;  
LedRow\_14 : Word;  
LedRow\_15 : Word;  
State : Int;  
Direction : Int;  
HeadX : Int;  
HeadY : Int;  
FoodX : Int;  
FoodY : Int;  
SnakeLength : Int;  
Score : Int;  
GameOver : Bool;  
GameTick : Bool;  
Win : Bool;  
BlinkCount : Int;  
END\_VAR

VAR  
NextDirection : Int;  
NewX : Int;  
NewY : Int;  
BitIndex : Int;  
LedBuffer : Array[0..15] of Word;  
GameTimer : TON;  
BlinkTimer : TON;  
BlinkVisible : Bool;  
BlinkTime : Time;  
SnakeX : Array[0..255] of Int;  
SnakeY : Array[0..255] of Int;  
Seed : Int;  
EatFood : Bool;  
SelfCollision : Bool;  
FoodOnSnake : Bool;  
NeedNewFood : Bool;  
FoodReady : Bool;  
END\_VAR

VAR\_TEMP  
i : Int;  
j : Int;  
Attempt : Int;  
BodyLimit : Int;  
END\_VAR

BEGIN // BEGIN indicates the start of writing SCL code.

```
IF #Reset THEN
    // Why:
    // Reset is the highest-priority command.
    // It must stop movement timing and blink timing, then rebuild a known safe state.
    RESET_TIMER(#GameTimer);
    RESET_TIMER(#BlinkTimer);
    #State := 10;
    #Direction := 1;
    #NextDirection := 1;
    #HeadX := 7;
    #HeadY := 7;
    #NewX := 7;
    #NewY := 7;
    #FoodX := 12;
    #FoodY := 7;
    #SnakeLength := 3;
    #Score := 0;
    #GameOver := FALSE;
    #GameTick := FALSE;
    #Win := FALSE;
    #Seed := 123;
    #EatFood := FALSE;
    #SelfCollision := FALSE;
    #FoodOnSnake := FALSE;
    #NeedNewFood := FALSE;
    #FoodReady := FALSE;
    #BlinkVisible := TRUE;
    #BlinkTime := T#500ms;
    #BlinkCount := 0;
    
    FOR #i := 0 TO 255 DO
        #SnakeX[#i] := 0;
        #SnakeY[#i] := 0;
    END_FOR;
    
    #SnakeX[0] := 7;
    #SnakeY[0] := 7;
    #SnakeX[1] := 6;
    #SnakeY[1] := 7;
    #SnakeX[2] := 5;
    #SnakeY[2] := 7;

ELSE
    // Why:
    // Pause should stop game movement only.
    // It must not clear the snake, food, score, or display state.
    #GameTimer(IN := (#State = 20) AND NOT #Pause, PT := #MoveTime);
    #GameTick := #GameTimer.Q;
    
    // Why:
    // Seed is continuously changed so that the next food position is not always identical.
    #Seed := (#Seed + 1) MOD 32767;
    
    CASE #State OF
        0:
            // Why:
            // State 0 is a recovery state.
            // If an undefined state occurs, the FB returns to a known idle state.
            RESET_TIMER(#GameTimer);
            RESET_TIMER(#BlinkTimer);
            #Direction := 1;
            #NextDirection := 1;
            #HeadX := 7;
            #HeadY := 7;
            #NewX := 7;
            #NewY := 7;
            #FoodX := 12;
            #FoodY := 7;
            #SnakeLength := 3;
            #Score := 0;
            #GameOver := FALSE;
            #GameTick := FALSE;
            #Win := FALSE;
            #Seed := 123;
            #EatFood := FALSE;
            #SelfCollision := FALSE;
            #NeedNewFood := FALSE;
            #FoodReady := FALSE;
            #BlinkVisible := TRUE;
            #BlinkTime := T#500ms;
            #BlinkCount := 0;
            
            FOR #i := 0 TO 255 DO
                #SnakeX[#i] := 0;
                #SnakeY[#i] := 0;
            END_FOR;
            
            #SnakeX[0] := 7;
            #SnakeY[0] := 7;
            #SnakeX[1] := 6;
            #SnakeY[1] := 7;
            #SnakeX[2] := 5;
            #SnakeY[2] := 7;
            #State := 10;
            
        10:
            // Why:
            // Idle rebuilds initial game data.
            // This keeps Start as a clean command that always begins from the same predictable state.
            RESET_TIMER(#BlinkTimer);
            #GameOver := FALSE;
            #Win := FALSE;
            #Direction := 1;
            #NextDirection := 1;
            #HeadX := 7;
            #HeadY := 7;
            #NewX := 7;
            #NewY := 7;
            #FoodX := 12;
            #FoodY := 7;
            #SnakeLength := 3;
            #Score := 0;
            #EatFood := FALSE;
            #SelfCollision := FALSE;
            #NeedNewFood := FALSE;
            #FoodReady := FALSE;
            #BlinkVisible := TRUE;
            #BlinkTime := T#500ms;
            #BlinkCount := 0;
            
            FOR #i := 0 TO 255 DO
                #SnakeX[#i] := 0;
                #SnakeY[#i] := 0;
            END_FOR;
            
            #SnakeX[0] := 7;
            #SnakeY[0] := 7;
            #SnakeX[1] := 6;
            #SnakeY[1] := 7;
            #SnakeX[2] := 5;
            #SnakeY[2] := 7;
            
            IF #Start THEN
                RESET_TIMER(#GameTimer);
                #State := 20;
            END_IF;
            
        20:
            // Why:
            // Direction input is stored in NextDirection first.
            // The actual Direction changes only on GameTick, so the snake moves one grid per timing event.
            IF #BtnUp AND (#Direction <> 2) THEN
                #NextDirection := 0;
            ELSIF #BtnRight AND (#Direction <> 3) THEN
                #NextDirection := 1;
            ELSIF #BtnDown AND (#Direction <> 0) THEN
                #NextDirection := 2;
            ELSIF #BtnLeft AND (#Direction <> 1) THEN
                #NextDirection := 3;
            END_IF;
            
            IF #GameTick AND NOT #Pause THEN
                #Direction := #NextDirection;
                #NewX := #SnakeX[0];
                #NewY := #SnakeY[0];
                
                CASE #Direction OF
                    0:
                        #NewY := #SnakeY[0] - 1;
                    1:
                        #NewX := #SnakeX[0] + 1;
                    2:
                        #NewY := #SnakeY[0] + 1;
                    3:
                        #NewX := #SnakeX[0] - 1;
                    ELSE
                        #NewX := #SnakeX[0];
                        #NewY := #SnakeY[0];
                END_CASE;
                
                // Why:
                // Wall-through mode converts an out-of-range position into the opposite side.
                // Wall collision is intentionally removed, while self-collision is preserved.
                IF #NewX < 0 THEN
                    #NewX := 15;
                ELSIF #NewX > 15 THEN
                    #NewX := 0;
                END_IF;
                
                IF #NewY < 0 THEN
                    #NewY := 15;
                ELSIF #NewY > 15 THEN
                    #NewY := 0;
                END_IF;
                
                #EatFood := FALSE;
                #SelfCollision := FALSE;
                #NeedNewFood := FALSE;
                
                IF (#NewX = #FoodX) AND (#NewY = #FoodY) THEN
                    #EatFood := TRUE;
                END_IF;
                
                // Why:
                // When food is not eaten, the tail will move away, so the old tail position is allowed.
                // When food is eaten, the tail remains, so the full body must be checked.
                IF #EatFood THEN
                    #BodyLimit := #SnakeLength - 1;
                ELSE
                    #BodyLimit := #SnakeLength - 2;
                END_IF;
                
                IF #BodyLimit >= 0 THEN
                    FOR #i := 0 TO #BodyLimit DO
                        IF (#NewX = #SnakeX[#i]) AND (#NewY = #SnakeY[#i]) THEN
                            #SelfCollision := TRUE;
                        END_IF;
                    END_FOR;
                END_IF;
                
                IF #SelfCollision THEN
                    #GameOver := TRUE;
                    #State := 30;
                    RESET_TIMER(#GameTimer);
                    RESET_TIMER(#BlinkTimer);
                    #BlinkVisible := TRUE;
                ELSE
                    IF #EatFood THEN
                        IF #SnakeLength < 256 THEN
                            // Why:
                            // When food is eaten, the snake grows.
                            // The copy starts from SnakeLength, preserving the old tail and creating one new body segment.
                            FOR #i := #SnakeLength TO 1 BY -1 DO
                                #SnakeX[#i] := #SnakeX[#i - 1];
                                #SnakeY[#i] := #SnakeY[#i - 1];
                            END_FOR;
                            
                            #SnakeX[0] := #NewX;
                            #SnakeY[0] := #NewY;
                            #SnakeLength := #SnakeLength + 1;
                            #Score := #Score + 1;
                            #NeedNewFood := TRUE;
                        ELSE
                            #Win := TRUE;
                            #GameOver := TRUE;
                            #State := 40;
                            RESET_TIMER(#GameTimer);
                            RESET_TIMER(#BlinkTimer);
                            #BlinkVisible := TRUE;
                        END_IF;
                    ELSE
                        // Why:
                        // Normal movement does not change snake length.
                        // Each body segment copies the previous segment, then the new head is committed.
                        IF #SnakeLength > 1 THEN
                            #BodyLimit := #SnakeLength - 1;
                            FOR #i := #BodyLimit TO 1 BY -1 DO
                                #SnakeX[#i] := #SnakeX[#i - 1];
                                #SnakeY[#i] := #SnakeY[#i - 1];
                            END_FOR;
                        END_IF;
                        
                        #SnakeX[0] := #NewX;
                        #SnakeY[0] := #NewY;
                    END_IF;
                    
                    #HeadX := #SnakeX[0];
                    #HeadY := #SnakeY[0];
                    
                    IF #NeedNewFood THEN
                        // Why:
                        // Food must not appear on the snake body.
                        // The loop tries pseudo-random positions until it finds a free cell.
                        #FoodReady := FALSE;
                        #Seed := (#Seed + 17) MOD 32767;
                        
                        FOR #Attempt := 0 TO 255 DO
                            #FoodX := #Seed MOD 16;
                            #FoodY := (#Seed / 16) MOD 16;
                            #FoodOnSnake := FALSE;
                            #BodyLimit := #SnakeLength - 1;
                            
                            FOR #j := 0 TO #BodyLimit DO
                                IF (#FoodX = #SnakeX[#j]) AND (#FoodY = #SnakeY[#j]) THEN
                                    #FoodOnSnake := TRUE;
                                END_IF;
                            END_FOR;
                            
                            IF NOT #FoodOnSnake THEN
                                #FoodReady := TRUE;
                                EXIT;
                            ELSE
                                #Seed := (#Seed + 31) MOD 32767;
                            END_IF;
                        END_FOR;
                        
                        IF NOT #FoodReady THEN
                            #Win := TRUE;
                            #GameOver := TRUE;
                            #State := 40;
                            RESET_TIMER(#GameTimer);
                            RESET_TIMER(#BlinkTimer);
                            #BlinkVisible := TRUE;
                        END_IF;
                    END_IF;
                    
                    RESET_TIMER(#GameTimer);
                END_IF;
            END_IF;
            
        30:
            // Why:
            // GameOver is a stopped state.
            // Movement is disabled, and the display layer uses BlinkVisible to show a 1Hz warning effect.
            #GameOver := TRUE;
            RESET_TIMER(#GameTimer);
            
            IF #Start THEN
                #Direction := 1;
                #NextDirection := 1;
                #HeadX := 7;
                #HeadY := 7;
                #NewX := 7;
                #NewY := 7;
                #FoodX := 12;
                #FoodY := 7;
                #SnakeLength := 3;
                #Score := 0;
                #GameOver := FALSE;
                #Win := FALSE;
                #EatFood := FALSE;
                #SelfCollision := FALSE;
                #NeedNewFood := FALSE;
                #FoodReady := FALSE;
                RESET_TIMER(#BlinkTimer);
                #BlinkVisible := TRUE;
                #BlinkCount := 0;
                
                FOR #i := 0 TO 255 DO
                    #SnakeX[#i] := 0;
                    #SnakeY[#i] := 0;
                END_FOR;
                
                #SnakeX[0] := 7;
                #SnakeY[0] := 7;
                #SnakeX[1] := 6;
                #SnakeY[1] := 7;
                #SnakeX[2] := 5;
                #SnakeY[2] := 7;
                RESET_TIMER(#GameTimer);
                #State := 20;
            END_IF;
            
        40:
            // Why:
            // Win is separated from GameOver because its display meaning is different.
            // The display layer uses a faster 2Hz full-screen blink to indicate completion.
            #Win := TRUE;
            #GameOver := TRUE;
            RESET_TIMER(#GameTimer);
            
            IF #Start THEN
                #Direction := 1;
                #NextDirection := 1;
                #HeadX := 7;
                #HeadY := 7;
                #NewX := 7;
                #NewY := 7;
                #FoodX := 12;
                #FoodY := 7;
                #SnakeLength := 3;
                #Score := 0;
                #GameOver := FALSE;
                #Win := FALSE;
                #EatFood := FALSE;
                #SelfCollision := FALSE;
                #NeedNewFood := FALSE;
                #FoodReady := FALSE;
                RESET_TIMER(#BlinkTimer);
                #BlinkVisible := TRUE;
                #BlinkCount := 0;
                
                FOR #i := 0 TO 255 DO
                    #SnakeX[#i] := 0;
                    #SnakeY[#i] := 0;
                END_FOR;
                
                #SnakeX[0] := 7;
                #SnakeY[0] := 7;
                #SnakeX[1] := 6;
                #SnakeY[1] := 7;
                #SnakeX[2] := 5;
                #SnakeY[2] := 7;
                RESET_TIMER(#GameTimer);
                #State := 20;
            END_IF;
            
        ELSE
            #State := 0;
    END_CASE;
    
    // Why:
    // Blinking is handled separately from game movement.
    // This prevents GameOver or Win animation from changing snake data, food data, or score.
    // BlinkCount is added as a diagnostic counter because BlinkTimer.Q may be TRUE for only one PLC scan.
    IF #State = 30 THEN
        // Why:
        // GameOver uses 1Hz blinking.
        // A 1Hz full cycle is 1s, so the visible state toggles every 500ms.
        #BlinkTime := T#500ms;
        #BlinkTimer(IN := TRUE, PT := #BlinkTime);
        
        IF #BlinkTimer.Q THEN
            #BlinkVisible := NOT #BlinkVisible;
            
            IF #BlinkCount < 32767 THEN
                #BlinkCount := #BlinkCount + 1;
            ELSE
                #BlinkCount := 0;
            END_IF;
            
            RESET_TIMER(#BlinkTimer);
        END_IF;
        
    ELSIF #State = 40 THEN
        // Why:
        // Win uses 2Hz blinking.
        // A 2Hz full cycle is 500ms, so the visible state toggles every 250ms.
        #BlinkTime := T#250ms;
        #BlinkTimer(IN := TRUE, PT := #BlinkTime);
        
        IF #BlinkTimer.Q THEN
            #BlinkVisible := NOT #BlinkVisible;
            
            IF #BlinkCount < 32767 THEN
                #BlinkCount := #BlinkCount + 1;
            ELSE
                #BlinkCount := 0;
            END_IF;
            
            RESET_TIMER(#BlinkTimer);
        END_IF;
        
    ELSE
        // Why:
        // In normal game states, blinking should not affect display.
        // The display must remain visible and the blink timer must be reset.
        RESET_TIMER(#BlinkTimer);
        #BlinkVisible := TRUE;
    END_IF;
END_IF;

// Why:
// LedBuffer works as a display buffer.
// The game calculates all visual points in memory, then writes all rows to physical outputs at the end.
FOR #i := 0 TO 15 DO
    #LedBuffer[#i] := WORD#16#0000;
END_FOR;

IF NOT #Reset THEN
    IF #State = 40 THEN
        // Why:
        // Win animation uses full-screen blinking because it is visually clearer than showing the final snake only.
        IF #BlinkVisible THEN
            FOR #i := 0 TO 15 DO
                #LedBuffer[#i] := WORD#16#FFFF;
            END_FOR;
        END_IF;
    ELSE
        // Why:
        // During GameOver, the normal drawing logic is still used, but it is gated by BlinkVisible.
        // This produces a 1Hz blink without modifying snake or food data.
        IF (#State <> 30) OR #BlinkVisible THEN
            IF (#FoodX >= 0) AND (#FoodX <= 15) AND (#FoodY >= 0) AND (#FoodY <= 15) THEN
                IF #FoodX <= 7 THEN
                    #BitIndex := #FoodX + 8;
                ELSE
                    #BitIndex := #FoodX - 8;
                END_IF;
                
                #LedBuffer[#FoodY] := #LedBuffer[#FoodY] OR SHL(IN := WORD#16#0001, N := #BitIndex);
            END_IF;
            
            IF #SnakeLength > 0 THEN
                #BodyLimit := #SnakeLength - 1;
                
                FOR #i := 0 TO #BodyLimit DO
                    IF (#SnakeX[#i] >= 0) AND (#SnakeX[#i] <= 15) AND (#SnakeY[#i] >= 0) AND (#SnakeY[#i] <= 15) THEN
                        // Why:
                        // The internal coordinate X=0..15 is not the same as the Siemens WORD bit position.
                        // This mapping keeps game logic independent from the physical QW/QB/bit order.
                        IF #SnakeX[#i] <= 7 THEN
                            #BitIndex := #SnakeX[#i] + 8;
                        ELSE
                            #BitIndex := #SnakeX[#i] - 8;
                        END_IF;
                        
                        #LedBuffer[#SnakeY[#i]] := #LedBuffer[#SnakeY[#i]] OR SHL(IN := WORD#16#0001, N := #BitIndex);
                    END_IF;
                END_FOR;
            END_IF;
        END_IF;
    END_IF;
END_IF;

// Why:
// All physical LED outputs are assigned only once at the end.
// This avoids output conflict and makes Watch Table debugging easier.
#LedRow_00 := #LedBuffer[0];
#LedRow_01 := #LedBuffer[1];
#LedRow_02 := #LedBuffer[2];
#LedRow_03 := #LedBuffer[3];
#LedRow_04 := #LedBuffer[4];
#LedRow_05 := #LedBuffer[5];
#LedRow_06 := #LedBuffer[6];
#LedRow_07 := #LedBuffer[7];
#LedRow_08 := #LedBuffer[8];
#LedRow_09 := #LedBuffer[9];
#LedRow_10 := #LedBuffer[10];
#LedRow_11 := #LedBuffer[11];
#LedRow_12 := #LedBuffer[12];
#LedRow_13 := #LedBuffer[13];
#LedRow_14 := #LedBuffer[14];
#LedRow_15 := #LedBuffer[15];

```

END\_FUNCTION\_BLOCK //This is the end of the entire SCL code.

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 27, 2026, 9:49am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/9 "2026-05-27T09:49:57Z")

</div>

Hi Bob Chen:

Regarding your first response, it’s not necessary to go into so much detail to simply explain that hardwired logic is not the same as programmed logic. Your post essentially says the same thing as this post, only in more detail and demonstrating why students make the mistakes they do…

> [@Contribution to the factory io project 123 (Programming with AI)](https://community.factoryio.com/t/contribution-to-the-factory-io-project-123-programming-with-ai/4571):
>
> --------------------------------------------------- English ---------------------------------------- WHY ARE PLCs PROGRAMMED AS IF THEY WERE PHYSICAL CONTROL PANELS INSTEAD OF USING PROGRAMMED LOGIC? This mindset is used because PLCs were created to replace electrical panels with relays and contactors (hardwired logic). Ladder logic was designed so that electricians could wire a PLC the same way they previously wired it. Many students simply copy electrical schematics into the PL…

Regarding your second response, look, only you, the one who wrote the code, understand that code you’re showing. Nobody else understands it. Now, what you need to do isn’t show your code; what you need to do is create a video at least 15 minutes long where the effectiveness of that code can be verified. The snake should exit from one side and enter from the opposite side, both from left to right and from top to bottom. If I don’t see that in a video, I’m going to think your code doesn’t work, and if I don’t analyze it from the start, if I don’t see the video, I’m going to completely ignore it. Obviously, I’m not speaking for myself, but for a student in my shoes…

Now, when I finish contribution 127, I’m going to try to make the Snake game with [Factory.io](http://Factory.io). I’ll try to do it in stages and upload it to my channel. The last video in the series will be the game running for at least 15 minutes. If this isn’t the case, I can’t say I’ve made the Snake game because it didn’t work. I’ll have learned, I’ll have tried, and whatever else you want to say, but if I’m not able to make a video of the Snake working, we can NEVER say the game is finished. In your case, it’s the same: if there aren’t videos of at least 15 minutes, then there’s no game. There will be other things, but they won’t be the Snake game working.

We’ll see how this turns out once I get started…

Regards

---

<div class="post-metadata">

**Author:** ![bob.chen](https://avatars.discourse-cdn.com/v4/letter/b/a6a055/32.png) [@bob.chen](https://community.factoryio.com/u/bob.chen)\
**Post date:** [May 27, 2026, 11:57am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/10 "2026-05-27T11:57:21Z")

</div>

Hi, Amjavi6,

Following your request, I added the wall-crossing function. The snake can now pass through the top, bottom, left, and right boundaries.

I also added a pause function, so the game can be paused during operation. In addition, I added a counter function to clearly show the game progress.

If the snake hits its own body during the game, it will trigger Game Over, and the display will flash at 1 Hz.

After the 7-minute mark in the video, I demonstrate how to modify the movement speed. The default value is 300 ms. In the video, I changed it to 100 ms, but the game became too fast and ended almost immediately. When adjusted to 600 ms, the speed became too slow.

This shows that the speed is adjustable, and the programmer can decide how to modify it depending on the desired gameplay.

Please refer to my personal YouTube video:

[![](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/4/49a33740928bd085e8f07280ea5a54df30d1039d.jpeg "PLC 也能做遊戲？用 Siemens SCL 控制 16x16 LED 貪食蛇") ](https://www.youtube.com/watch?v=fZHVhxrbnCY)

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 27, 2026, 4:42pm UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/11 "2026-05-27T16:42:47Z")

</div>

Hi Bob Chen:

Now we can truly say you created the Snake game, congratulations!

I’m going to give it a try. To start, I’ll use 100 LEDs, 10 x 10 instead of 16 x 16, and see how it goes. We’ll see what happens…

We’ll demonstrate to the students that FactoryIO is the best simulator available today.

Regards

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 27, 2026, 8:37pm UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/12 "2026-05-27T20:37:36Z")

</div>

Hi Bob Chen:

Let’s see how the system behaves in the tests; it seems to be working well, now we’ll perfect it with the construction details:

Since I had the 100 bit program, I used it, but given the results, since it only works with the first and last digits of the list, it makes no difference whether it’s 10 x 10 or 16 x 16.

[![](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/c/c813288fb748220edb78d0b21cf0aea55f5436f9.jpeg "SNAKE PRUEBA") ](https://www.youtube.com/watch?v=0ItbVv9ahkI)

Regards

---

<div class="post-metadata">

**Author:** ![bernard.riera](https://avatars.discourse-cdn.com/v4/letter/b/dbc845/32.png) [@bernard.riera](https://community.factoryio.com/u/bernard.riera)\
**Post date:** [May 29, 2026, 7:55am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/13 "2026-05-29T07:55:04Z")

</div>

very very cool, congratulations !

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 29, 2026, 10:15am UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/14 "2026-05-29T10:15:39Z")

</div>

Hello:

[![](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/b/bf9f7721ce5be7d7d74af2a4b72b8674602d2bda.jpeg "FACTORY IO JUEGO SERPIENTE (SNAKE) Schneider") ](https://www.youtube.com/watch?v=fCQA4S8UjkM)

For this system to work, the food’s coordinates must be a multiple of 9; otherwise, it’s placed outside the line and activated if the snake passes through the adjacent row or column.

The bits that need to be controlled are the first one in the list, which should be turned off, and the last three, which should be turned on for safety. When the snake eats, two pieces are added at once, so the snake’s length doesn’t matter; only three bits are controlled.

In any case, the goal is to understand the game’s programming, not to create a perfect game. As you can see, a 10x10 board is sufficient.

Obviously, this can’t be done with hardwired logic; it must be done with programmed logic.

Regards

---

<div class="post-metadata">

**Author:** ![amjavi6](https://dub1.discourse-cdn.com/flex017/user_avatar/community.factoryio.com/amjavi6/32/755_2.png) [@amjavi6](https://community.factoryio.com/u/amjavi6)\
**Post date:** [May 29, 2026, 1:47pm UTC](https://community.factoryio.com/t/collaborated-with-ai-to-develop-a-snake-game/4592/15 "2026-05-29T13:47:32Z")

</div>

Hello:

Request to add a rating to each meal. I’m also taking this opportunity to make sure everything is working correctly after renaming the project. Here’s the result:

[![](https://europe1.discourse-cdn.com/flex017/uploads/factoryio/original/2X/a/a4c57b4d5960d512898de7554315dbe5e6d0f52b.jpeg "FACTORY IO JUEGO SERPIENTE PLC (SNAKE) Schneider P2") ](https://www.youtube.com/watch?v=ZUy8IrexZV4)

Regards
