14
\$\begingroup\$

In the classic game of Sokoban the player's character solves puzzles by pushing boxes around a warehouse onto designated targets. Take, for example, this ASCIIfied game level:

...x. ..B.. x.A.. ..... ..o.. 

This level could be solved by the player (o) pushing box A left 2 spaces onto the target x, then pushing box B up 1 space and right 1 space onto the other x. (This is one of many possible solutions.)

Stuck

The player has two important limitations, though: They can only push boxes (no pulling) and they can only push one box at a time. It's therefore possible to get "stuck":

...x. ..... x.o.B ....A ..... 

Here the player can't push A upward or B downward because the other box is in the way. They can't push either to the right because they're up against the "wall" (the boundary of the level). And they can't pull at all. They're stuck.

The challenge

For this challenge we'll look at a simplified version of the game with no targets. There are only boxes and a question to be answered: Could any of these boxes be moved?

Input

The input will be a rectangular matrix of arbitrary size with some elements representing boxes (say, 1s) and others representing empty spaces (0s), e.g.:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 

or

0 0 0 1 0 0 0 0 0 

In practice, the input could be an array of arrays of integers or booleans, a string of 1s and 0s on multiple lines, or any other reasonable representation.

The input will always have at least one box (1) and at least one empty space (0).

Output

The output should be a boolean, or a designated "yes" value or "no" value, indicating whether, if a player was dropped somewhere in the level, they would be able to move any box. (By "dropped somewhere" we mean any empty space, even if in a real game of Sokoban it would not be possible for the player to arrive at that spot.)

Rules

Test cases

Input: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 Output: false 
Input: 0 0 0 1 0 0 0 0 0 Output: true 
Input: 1 0 0 0 0 0 0 0 1 Output: false 
Input: 1 1 1 1 1 0 1 0 1 1 1 1 Output: true 
Input: 1 1 1 1 1 0 1 1 1 1 1 1 Output: false 
Input: 1 0 Output: false 
Input: 0 1 0 1 Output: true 
Input: 0 1 1 0 Output: false 
\$\endgroup\$
5
  • \$\begingroup\$ Sandbox \$\endgroup\$ Commented Aug 28, 2024 at 18:15
  • 2
    \$\begingroup\$ Dropped somewhere means 'there exist an empty spot somewhere where the player can be dropped'? Note that there exist configurations where the player may or may not be able to move a box depending on where they start. \$\endgroup\$ Commented Aug 29, 2024 at 9:38
  • \$\begingroup\$ Maybe another good test case would be to put a 2x2 block of 1's in the middle of at least a 4x4 grid. Or to make it a bit more complicated, something like 0 0 0 0 0 / 0 1 1 0 0 / 0 1 1 1 0 / 0 0 1 1 0 / 0 0 0 0 0 \$\endgroup\$ Commented Aug 29, 2024 at 17:01
  • \$\begingroup\$ Can the input use any two consistent values, or must they be 0/1? \$\endgroup\$ Commented Aug 29, 2024 at 17:13
  • \$\begingroup\$ @aeh5040 Yes, it can be any two consistent values. \$\endgroup\$ Commented Aug 29, 2024 at 17:29

18 Answers 18

5
\$\begingroup\$

Wolfram Language (Mathematica), 28 bytes

FreeQ[#|#,{___,,1,,___}]& 

Try it online!

Input an array with 1 for boxes and Null for empty spaces. Returns True if all boxes are stuck, and False otherwise. is \[Transpose].

\$\endgroup\$
5
\$\begingroup\$

Stax, 9 bytes

üo∟Ö╘└└⌂o 

Run and debug it at staxlang.xyz! with printable 0/1 output

Takes input as a list of rows, each cell represented as integer 1 for empty and 2 for box. Output is falsy for stuck and truthy for not stuck.

I might be able to get away with â╧óΩ═àN♣ for 8 bytes, replacing the unpacked |I with just I. That would produce -1 for stuck and >=0 for not stuck, not quite as much a designated "yes" value or "no" value as falsy/truthy.

Unpacked (10 bytes)

cM+J11JE|I cM+ concat with transposed copy J join on 0 11JE literal [1,2,1] |I all indices of subarray 
\$\endgroup\$
5
\$\begingroup\$

Python, 95 81 bytes

lambda d:any(x>d[x,y]>d[x-1,y]<d[x-2,y]or y>d[x,y]>d[x,y-1]<d[x,y-2]for(x,y)in d) 

Attempt This Online!

Takes a dict, with keys being (x, y) coordinates and values being either 0 for box or 1 for empty space. Returns false for stuck and true for non-stuck. -14 bytes after switching from (x, y) being the center square to (x, y) being the bottom right square, taking inspiration from Arnauld's JavaScript answer where (x, y) is the top left square.

Explanation: as was stated in the sandbox, this challenge boils down to finding a horizontal or vertical instance of the pattern [space] [box] [space] (one of the spaces gets filled in by the player; the box gets pushed into the other space). The code inspects every square to see if it's either the rightmost square of a horizontal (1, 0, 1) or the bottom square of a vertical (1, 0, 1). We can write (c, b, a) == (1, 0, 1) as c > b and b < a -- or (ab)using Python's chained comparisons, c > b < a.

There's a subtle, but neat thing going on with the short circuiting in the chained comparisons here: the x> and y> are there to prevent out-of-bounds indexing. If x = 0, then x>d[x,y] cannot be true, so d[x-1,y] and d[x-2,y] are never evaluated. If x = 1, then x>d[x,y]>d[x-1,y] cannot be true, so d[x-2,y] is never evaluated. (Recall that d[a,b] is always either 0 or 1.) The same logic applies to the other chain of comparisons.

\$\endgroup\$
5
\$\begingroup\$

Jelly, 7 bytes

;ZK“]»ẇ 

A monadic Link that accepts a list of lists of characters:

  • A : space
  • B : box

and yields an integer:

  • \$1\$ : possible
  • \$0\$ : impossible

Try it online! Or see the test-suite.

How?

;ZK“]»ẇ - Link: list of lists of characters, Rows Z - transpose {Rows} -> Columns ; - {Rows} concatenate {Columns} K - join {that} with space characters “]» - compressed string = "ABA" ẇ - is {"ABA"} a sublist of {the space separated rows & columns}? 

Also \$7\$ bytes accepting a list of lists of integers where \$1\$ are spaces and \$0\$ are boxes:

;ZK5B¤ẇ ¤ - nilad followed by link(s) as a nilad: 5 - five B - cast to binary -> [1,0,1] 

Try it online!

\$\endgroup\$
5
\$\begingroup\$

JavaScript (ES11), 73 bytes

-2 thanks to shape warrior t

Expects a binary matrix (1 for empty, 0 for filled) and returns a Boolean value.

m=>m.some((r,y)=>r.some((v,x)=>v&&r[x+1]<r[x+2]|m[y+1]?.[x]<m[y+2]?.[x])) 

Attempt This Online!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ -2 bytes by replacing both instances of !a&b with a<b (which returns false if either operand is undefined instead of treating undefined as 0, but this doesn't affect correctness). \$\endgroup\$ Commented Aug 29, 2024 at 13:40
4
\$\begingroup\$

Nekomata + -e, 7 bytes

Ť?~q5Ƃ= 

Attempt This Online!

Take input as a list of lists with 0 for boxes and 1 for empty spaces.

Ť?~q5Ƃ= Ť? Optionally transpose the input ~ Take any row q Take any contiguous subsequence 5Ƃ= Check if it is equal to [1,0,1] 

-e checks if there is a solution.

\$\endgroup\$
4
\$\begingroup\$

05AB1E (legacy), 6 bytes

ø«»тÁå 

Input as a list of strings without delimiter, using 1/0 like in the challenge description.

Try it online or verify all test cases.

Explanation:

ø # Zip/transpose the (implicit) input-list of strings, swapping rows/columns « # Merge it to the (implicit) input-list » # Join this list of strings by newlines т # Push 100 Á # Rotate it once towards the right: "010" å # Check if the multiline string contains "010" as substring # (after which the result is output implicitly) 

Uses the legacy version of 05AB1E, since ø works on a list of strings, whereas the new 05AB1E version would require a matrix (in the new version of 05AB1E, taking a matrix of bits as input, this would have been +1 byte: ø«J»тÁå).

\$\endgroup\$
3
\$\begingroup\$

K (ngn/k), 19 bytes

It takes input as array of characters, and checks if every row and column stays the same when splitted with 010. It outputs 0 for yes instance and 1 for no instance.

{i~,/"010"\'i:x,+x} 

Try it online!

\$\endgroup\$
2
\$\begingroup\$

Charcoal, 20 bytes

WS⊞υι⊙⁺υEθ⭆υ§λκ№ι010 

Try it online! Link is to verbose version of code. Takes input as a string of 0s and 1s with a newline after each row and outputs a Charcoal boolean, i.e. - if a box can be moved, nothing if not. Explanation:

WS⊞υι 

Input the level.

⊙⁺υEθ⭆υ§λκ№ι010 

Concatenate it to its transpose and check any of the rows for the substring 010.

(My experimental transpose branch of Charcoal could perform the transpose operation in five fewer bytes.)

\$\endgroup\$
2
\$\begingroup\$

JavaScript (Node.js), 52 bytes

x=>x.match(`010|0[^]{${n=x.indexOf` `}}1[^]{${n}}0`) 

Try it online!

Regex-based

\$\endgroup\$
1
  • \$\begingroup\$ I didn’t know about [^]. Nice trick. \$\endgroup\$ Commented Aug 29, 2024 at 1:31
2
\$\begingroup\$

APL(Dyalog Unicode), 15 bytes SBCS

A 3-train that checks if 0 1 0 is a substring of any row or column.

⊢∨⍥(1∊0 1 0∘⍷)⍉ 

Try it on APLgolf!

\$\endgroup\$
2
\$\begingroup\$

Python, 53 55 bytes

Thanks @att for finding a bug (+2).

lambda d:"010"in((n:=d.find("\n"))*(d+n*"\n"))[::n+1]+d 

Attempt This Online!

Expects a single string consisting of newline-terminated lines of 0s and 1s.

Python, 65 62 bytes

Thanks @att for -3.

lambda d:any((0,1,0)in zip(a,a[1:],a[2:])for a in[*zip(*d)]+d) 

Attempt This Online!

Expects a list-of-lists of integers.

Any similarities of test code with that of shape warrior t's are mere coincidence =-P

How?

Essentially an exercise in performing the transpose.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ 1st is broken on e.g. tc4 transposed since [] has higher precedence than *. -3 on the second \$\endgroup\$ Commented Aug 29, 2024 at 3:07
2
\$\begingroup\$

Scala 3, 129 101 bytes

Saved 28 bytes thanks to @shape warrior t


Golfed version. Attempt This Online!

g=>g.keys.exists{case(x,y)=>x>1&&g((x,y))*g((x-2,y))>g((x-1,y))||y>1&&g((x,y))*g((x,y-2))>g((x,y-1))} 

Ungolfed version. Attempt This Online!

object Main { def checkPattern(grid: Map[(Int, Int), Int]): Boolean = { for ((x, y) <- grid.keys) { if (x > 1 && grid((x, y)) > grid((x - 1, y)) && grid((x - 1, y)) < grid((x - 2, y))) { return true } if (y > 1 && grid((x, y)) > grid((x, y - 1)) && grid((x, y - 1)) < grid((x, y - 2))) { return true } } false } def convert(testcase: String): Map[(Int, Int), Int] = { val rows = testcase.stripMargin.trim.split("\n") (for { (row, y) <- rows.zipWithIndex if row.trim.nonEmpty (char, x) <- row.trim.split("\\s+").zipWithIndex } yield (x, y) -> (1 - char.toInt)).toMap } def main(args: Array[String]): Unit = { val testcases = List( """ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 """, """ 0 0 0 1 0 0 0 0 0 """, """ 1 0 0 0 0 0 0 0 1 """, """ 1 1 1 1 1 0 1 0 1 1 1 1 """, """ 1 1 1 1 1 0 1 1 1 1 1 1 """, "1 0", "0 1 0 1", "0 1 1 0" ) for (testcase <- testcases) { println(s"Input:\n ${testcase.trim}\n\nOutput: ${checkPattern(convert(testcase))}\n") } } } 
\$\endgroup\$
2
  • \$\begingroup\$ Comparing against (1,0,1) is at least 1 byte shorter than doing a chained comparison, and you don't need the parentheses around && since it binds more tightly than ||. \$\endgroup\$ Commented Aug 29, 2024 at 12:38
  • \$\begingroup\$ ...Actually, in the absence of chained comparisons, the shortest way to test (a, b, c) = (1, 0, 1) (with a, b, c in {0, 1}) in most languages should be a*c>b. To test (a, b, c) = (0, 1, 0), a+c<b. \$\endgroup\$ Commented Aug 29, 2024 at 12:59
2
\$\begingroup\$

GNU sed, 53 bytes

s/^|\n/f/g :a /010|f0[01]*f1[01]*f0/ct s/f./f/g ta 

Online Editor. Run using gsed -zEf <program>. The program itself is 51 bytes; adding 2 bytes for options zE. Reads input of form

000 111 000 

Outputs t if a move exists, f if we are stuck.

Explanation

  • -z option reads the entire input file into pattern space
  • s/^|\n/f/g reshapes input to format f000f111f000
  • /010|f0[01]*f1[01]*f0/ tests if any row or first column contains sequence 010
    • On match, ct replaces pattern with t. Following substitution then fails and program exits
    • On no match, s/f./f/g removes the first column and we try the match again. When no data remains, this substitution then removes fs until one is left, then fails allowing the program to exit.
\$\endgroup\$
1
\$\begingroup\$

Uiua, 23 bytes

¬≍×⍉∩(>1×/+◫¯3⊂⊂,,1)⍉.. 

Try it!

¬≍×⍉∩(>1×/+◫¯3⊂⊂,,1)⍉.. ∩( )⍉. # for both the array and its transposition: ⊂⊂,,1 # join a row of 1s above and below it /+◫¯3 # sum three windows of it >1× # and find the cells that are boxes have sums >1 ⍉ # de-transpose the transposition × # find the cells that are stuck on both ¬≍ # and check if they don' match the original array 
\$\endgroup\$
2
  • \$\begingroup\$ 14 bytes ±⧻⊚↥∩⌕⍉,,¤◿2⇡3 \$\endgroup\$ Commented Aug 30, 2024 at 12:34
  • \$\begingroup\$ Or 12 bytes if you swap 1s and 0s in input and start with bits5 instead of mod2ran3 \$\endgroup\$ Commented Aug 30, 2024 at 12:36
1
\$\begingroup\$

Retina 0.8.2, 57 bytes

1`010|(?<=(.)*)0.*¶(?<2-1>.)*(?(1)^)1.*¶(?<-2>.)*(?(2)^)0 

Try it online! Takes input as a string of 0s and 1s with newlines between each row. Explanation:

1` 

Only bother checking for one match.

010| 

Search for the string 010 horizontally, or...

(?<=(.)*)0.*¶(?<2-1>.)*(?(1)^)1.*¶(?<-2>.)*(?(2)^)0 

... search for the string 010 vertically.

\$\endgroup\$
1
\$\begingroup\$

Ruby, 41 bytes

->s{(s+s.transpose).any?{|l|/010/=~l*''}} 

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Google Sheets, 101 bytes

=let(m,A1:F6,f,lambda(a,join(,index(if(len(a),a,"_")))),regexmatch(f(torow(m))&f(tocol(m,,1)),"010")) 

Put the matrix in cells A1:E5 and the formula in cell A7.

screenshot

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.