# Minecraft piston pushing

In Minecraft, pistons are blocks that can move other blocks. However, they can only move at most twelve blocks, and won't work at all if any obsidian is in the way. Also, slime blocks are sticky and stick to other blocks, except for obsidian.

Take as input a 5x5 matrix or similar, containing one piston at the bottom, and output the result of the single piston pushing upwards, as 5x6. Input format should have five distinct values for obsidian, slime, other block, piston and empty space, here represented by O, S,B,P, and   respectively.

  B
B
B
B
P


Output the result, if the piston was activated, as 5x6:

  B
B
B
B
P



Note that the whole configuration with the piston is moved up, as long as it is legal

# Testcases:


BBBBB
BBBBB
SSSSS
S
P
=>

BBBBB
BBBBB
SSSSS
S
P
Because 16 is too many blocks.

SBS
SBS
SBS
SBS
P
=>
B
SBS
SBS
SBS
SPS
Because slime sticks to blocks, but blocks don't stick to slime.
O
SSSSS
S
S
P
=>
O
SSSSS
S
S
P

Because the obsidian is in the way, the whole thing can't move.
S S S

S S S

S P S
=>

S S S

S S S
P
S   S

The stray blocks stay where they are.

SSSSS
B S B
B S B
B S B
B P B
=>
SSSSS
B S B
S
B S B
B P B
B   B
The slime pulls the first two blocks, but the rest stay in place.

OSO
OSO
OSO
OSO
OPO
=>
S
OSO
OSO
OSO
OPO
O O
Slime doesn't stick to obsidian, so it's free to move.


# Scoring

This is , the shorter the better!

• Sandboxed – A username May 14 at 22:27
• I was about to ask what was going on in test case 5, until I saw the spaces :P – Unrelated String May 14 at 23:23

# J, 121 98 bytes

Takes in  POSB as digits 01234. Probably some microgolfs left, these long tacit definitions tend to get confusing. :-)

((0,~*)+0,(*-.))[(]*(2 e.*&,)<14>+/@,@])[(]*@+(2&<@[*[:+/((,-)=i.2)t]*3=[)+*@[*1(t=:|.!.0)])^:_=&1


Try it online!

### How it works

• =&1 Start a will-be-pushed bitmap with the piston
• ^:_ expand the bitmap until it does not expand further:
• 1(t=:|.!.0)] Push the bitmap upwards (non-wrapping, thus !.0)
• *@[* but only if a non-space was pushed.
• t]*3=[ Extend push of the will-be-pushed slimes towards
• ((,-)=i.2) all 4 direction
• 2&<@[*[:+ but only push slimes and blocks.
• ]*@+…+ OR old bitmap, slime pushes, and up pushes for the next iteration.
• [(]*…) Set the final bitmap to 0 if …
• (2 e.#~&,) There is an obsidian block in it
• <14>+/@,@] or the sum (including the piston) is not less 14.
• 0,(*-.) Original map * not-pushed bitmap and an empty row above.
• (0,~*) Original map * pushed bitmap and an empty row under.
• + Add both maps – as empty spaces are 0 they don't overlap.
• Well done and nice explanation. – Jonah May 15 at 14:23
• I think 13 should be 14. – Jonathan Allan May 15 at 21:47

# Charcoal, 144 120 bytes

ＷＳ⊞υι≔⪫υ¶θＰθ…θ⌕θP≔⟦⟦ⅈⅉ⟧⟧ηＦη«Ｊ§ι⁰⊖§ι¹Ｆ‹@ＫＫ⊞η⟦ⅈⅉ⟧↓Ｆ⁼SＫＫＦ³«Ｍ✳⊗⁺²λＦ№⪪BS¹ＫＫ⊞η⟦ⅈⅉ⟧Ｍ✳⊗λ» »Ｆη«Ｊ§ι⁰⁻§ι¹›‹Ｌη¹⁴⊙η⁼O§§υ§ι¹§ι⁰§§υ⊟ι⊟ι


Try it online! Link is to verbose version of code. Explanation:

ＷＳ⊞υι


Input the layout. (I've decided to take input as newline-terminated lines; this allows you to experiment with layouts of different numbers of lines if you so wish.)

≔⪫υ¶θＰθ…θ⌕θP


Output the layout and move the cursor to the position of the piston.

≔⟦⟦ⅈⅉ⟧⟧ηＦη«


Perform a breadth-first search starting with the position of the piston.

Ｊ§ι⁰⊖§ι¹


Ｆ‹@ＫＫ⊞η⟦ⅈⅉ⟧


Add this position to the list unless it's empty.

↓Ｆ⁼SＫＫＦ³«


If the current position is a sticky block, then check the other three sides too.

Ｍ✳⊗⁺²λＦ№⪪BS¹ＫＫ⊞η⟦ⅈⅉ⟧Ｍ✳⊗λ


If there is a (sticky) block on that side then add this position to the list.

» »


Erase the block, assuming it will be moved. This also avoids considering the same block twice.

Ｆη«


Loop over each block.

Ｊ§ι⁰⁻§ι¹›‹Ｌη¹⁴⊙η⁼O§§υ§ι¹§ι⁰


Jump to its old or new position depending on whether there weren't too many blocks to push or any obsidian in the way.

§§υ⊟ι⊟ι


Print the block.