Verifying a horizontal ASCII pet snake

JavaScript (ES2018), 62 54 bytes

s=>!/0(.{30}|.{60,62}(.{31})*)0|( .{30}){4} /s.test(s)

Input is a single string:

  • without trailing new line
  • contain only space, '0', and '\n'
  • 30 characters each line, 5 lines, 154 character in total

Flag s means a dot match anything (including '\n'). This feature currently supported by Chrome 63+, Opera 50+, Safari 11.1+, based on compat-table. You may test this function with these supported browser. You will get an exception when loading the page if your browser do not support this feature.

How it works:

  • No column with no 0:
    • do not match /( .{30}){4} /
  • No two 0s in one column:
    • do not match /0.{30}(.{31})*0/
  • No 0 not connect to its neighbors:
    • do not match /0.{60}(.{31})*0/, /0.{62}(.{31})*0/

Merge all these regex, and you will finally got this one.

f =

s=>!/0(.{30}|.{60,62}(.{31})*)0|( .{30}){4} /s.test(s)
<textarea id=i style="width:100%;height:100px">            0 0               
  0        0 0 000            
00 0     00       000 0      0
    000 0            0 0   00 
       0                000   </textarea>
<button onclick="o.value=f(i.value.slice(0,154))">Validate</button>
Result: <output id=o></output>

Thanks to Martin Ender point out that do a single ! operator may save 8 bytes.


SnakeEx, 51 bytes

This is obviously the right language for the task. :^D

s:({c<L>}{c<R>}0[(<R> <L>)(<L> <R>)_?])%{30}
c:0 *$

Matches the entire input if it is a valid snake; fails to match if it's not. Try it here!

Explanation

SnakeEx is a 2-D pattern matching language. A program consists of a list of definitions for "snakes," which crawl around the input matching characters, changing directions, and spawning other snakes. In our program, we define two snakes, s and c.

We'll start with c because it's simpler. Its definition is 0 *$, which should be quite readable if you know regex: match 0, followed by zero or more spaces, followed by the edge of the grid. The main catch here: this matching can proceed in any direction. We're going to use c both upward and downward from the snake, to verify that there are no extra 0s in each column.

Now for the main snake, s. It takes the form (...)%{30}, which means "match the contents of the parentheses 30 times"--once for each 0 in the snake. So far, so good. What goes inside the parentheses?

{c<L>}

This spawns a new c snake, turned left 90 degrees. The direction is relative to the s snake's direction, so the new snake moves toward the top of the grid (the main snake is moving toward the right). The c snake checks that the current grid cell is a 0 and that every cell above it is a space. If it fails, the whole match fails. If it succeeds, we continue with

{c<R>}

which does the same thing, only turned right (toward the bottom of the grid).

Note that these spawns don't affect the position of the match pointer in the main snake. They're a bit like lookaheads in regex. (Maybe here we could call them "lookbesides"?) So after verifying that we're pointing to a 0 and the rest of the column contains only spaces, we need to actually match the 0:

0

Now the match pointer is on the character to the right of the 0. We need to check three different options: the snake angles down, the snake angles up, or the snake goes straight. For this, we can use an OR expression:

[...]

Inside our OR, we have three possibilities:

(<R> <L>)

Turn right, match a space, and turn left again (snake angles down).

(<L> <R>)

Turn left, match a space, and turn right again (snake angles up).

_?

Match zero or one underscores. Since there are no underscores in the input, this will always be an empty match (snake goes straight).

After matching one of the above three options, the match pointer should be pointing to the 0 in the next column, ready to match the parenthesized expression again.


Husk, 12 bytes

Depending on rule clarifications, may be 11 bytes or 13 bytes.

±Λ=;1Ẋ×≈mηfT

Try it online!

Input is a list of lines containing only spaces and 0s; if a single string is required, prepend to the program to split into lines. The TIO link already does this for clarity. Output is 0 or 1; if any falsy and truthy values are fine, the ± can be removed.

Explanation

±Λ=;1Ẋ×≈mηfT  Implicit input: a list of lines.
           T  Transpose into list of columns.
        m     For each column,
         ηf   get list of indices of truthy elements.
              In Husk, whitespace characters are falsy and other are truthy,
              so this gets us the indices of 0s on each column.
     Ẋ        For each adjacent pair of these index lists,
      ×       for all pairs drawn from the two lists,
       ≈      give 1 if they differ by at most 1, otherwise 0.
              For each adjacent pair, this produces a list of 1s and 0s.
 Λ            Do all of these lists
  =;1         equal [1]? Return either 0 or 30 (length of the outer list + 1).
±             Signum; convert to 0 or 1.

The idea is to use ×≈ to guarantee that (a) all columns contain precisely one 0, and (b) their positions differ by at most one. As an example, consider the 8-column input

0  0  0 
 000 0  
  00   0

First, mηfT transforms it to the list of index lists

[[1],[2],[2,3],[1,2,3],[],[2],[1],[3]]

Then Ẋ×≈ gives

[[1],[1,1],[1,1,0,1,1,1],[],[],[1],[0]]

Each 1 corresponds to a pair of indices that differ by at most 1, and each 0 corresponds to a pair that doesn't. Each result equals [1] precisely when both lists have one index, and the indices differ by at most 1.