# Detect ASCII-art windows made of M and S characters

A Window is an ASCII-art square with odd side length of at least 3, with a single character border around the edge as well as vertical and horizontal strokes in the middle:

#######
#  #  #
#  #  #
#######
#  #  #
#  #  #
#######


An MS Window is a window where the border is made only of the characters M and S. Your task is to write a program (or function) that takes a string and outputs a truthy value if the input is a valid MS Window, and a falsey value if it is not.

# Specifications

• You may take the input as a newline-separated string or an array of strings representing each line.
• The border of an MS Window may contain a mix of M and S characters, but the inside will always be composed of spaces.
• You can choose to detect only windows with trailing newlines, or only windows without trailing newlines, but not both.

# Test Cases

Truthy:

MMM
MMM
MMM

SMSMS
M M S
SMSMM
S S M
SMSMS

MMMMMMM
M  S  M
M  S  M
MSSSSSM
M  S  M
M  S  M
MMMMMMM


Falsey:

Hello, World!

MMMM
MSSM
MS M
MMMM

MMSMM
M S.M
sSSSS
M S M
MMSMM

MMMMMMM
M  M  M
MMMMMMM
M  M  M
MMMMMMM

MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM

MMSSMSSMM
M   M   M
S   S   S
S   S  S
MMSSMSSMM
S   S   S
S   S   S
M   M   M
MMSSMSSMM

• This is a great twist on ASCII arts, a decision problem to detect a certain structure.
– xnor
Jan 8, 2017 at 5:53
• @xnor I feel like we might want a different tag for reverse ASCII art like this. Jan 8, 2017 at 5:55
• while not specific to ascii art, pattern matching might be a good choice for a new tag Jan 8, 2017 at 6:15
• Can you add a test case or two where the string doesn't form a rectangular array? Jan 8, 2017 at 8:54
• @Mast, you are quite right! Maybe the challenge needs clarifying Jan 8, 2017 at 17:19

## Retina, 68 67 bytes

Byte count assumes ISO 8859-1 encoding.

S
M
^(M((M)*M)\2)((?<-9>¶M((?<9-3> )*(?(3)!)M|\5)\5)*(?(9)!)¶\1)\4$ Try it online! ## Grime, 39 38 bytes Thanks to Zgarb for saving 1 byte. eBB/BB/W+ W/+ B=W|B/W\ * W/\ /* W=[MS  Try it online! I'm not sure whether there's a simpler way to enforce the square aspect ratio of the individual window components than using a recursive nonterminal, but this seems to be working quite well. ### Explanation It's best to read the program from the bottom up. W=[MS  This simply defines a nonterminal (which you can think of as a subroutine that matches a rectangle) W which matches either an M or an S (there's an implicit ] at the end of the line). B=W|B/W\ * W/\ /*  This defines a non-terminal B which matches about a quarter of the output, i.e. one window panel with the left and top border. Something like this: MSM S M  To ensure that this window panel is square, we define B recursively. It's either a window character W, or it's B/W\ * W/\ /* which adds one layer to the right and to the bottom. To see how it does this, let's remove some syntactic sugar: (B/W[ ]*)(W/[ ]/*)  This is the same, because horizontal concatenation can be written either AB or A B, but the latter has lower precedence than the vertical concatenation / while for the former has higher. So B/W[ ]* is a B with a window character and a row of spaces below. And then we horizontally append W/[ ]/* which is a window character with a column of spaces. Finally, we assemble these nonterminals into the final window shape: BB/BB/W+ W/+  That's four window panels B followed by a row of window characters and a column of window characters. Note that we make no explicit assertion that the four window panels are the same size, but if they aren't it's impossible to concatenate them into rectangle. Finally the e at the beginning is simply a configuration which tells Grime to check that the entire input can be matched by this pattern (and it prints 0 or 1 accordingly). ## JavaScript (ES6), 115 113 bytes a=>(l=a.length)&a.every((b,i)=>b.length==l&b.every((c,j)=>(i&&l+~i-i&&l+~i&&j&&l+~j-j&&l+~j?/ /:/[MS]/).test(c)))  Takes input as a an array of arrays of characters (add 5 bytes for an array of strings) and returns 1 or 0. After verifying that the height is odd, every row is checked to ensure the array is square, and every character is verified to be one of the character(s) that we expect in that particular position. Edit: Saved 2 bytes thanks to @PatrickRoberts. • You can change (...).includes(c) to ~(...).search(c) to save 1 byte Jan 8, 2017 at 21:15 • Actually, even better you can change it to (...?/ /:/[MS]/).test(c) to save 2 bytes instead of just 1. Jan 8, 2017 at 21:24 • @PatrickRoberts Cute, thanks! – Neil Jan 8, 2017 at 21:28 # Perl, 1241231199593 84 The following Perl script reads one candidate MS Window from the standard input. It then exits with a zero exit status if the candidate is an MS Window and with a non-zero exit status if it isn't. It works by generating two regular expressions, one for the top, middle and bottom line and one for every other line, and checking the input against them. Thanks, @Dada. And again. map{$s=$"x(($.-3)/2);$m="[MS]";($c++%($#a/2)?/^$m$s$m$s$m$/:/^${m}{$.}$/)||die}@a=<>

• I'm not sure giving the result as exit status is allowed (I don't have time to look for the relevant meta post though). Regardless, you can save a few bytes: @a=<>;$s=$"x(($.-3)/2);$m="[MS]";map{$a[$_]!~($_%($./2)?"$m$s$m$s$m":"$m${m}{$.}")&&die}0..--$. – Dada Jan 8, 2017 at 12:44 • @Dada: Thanks! That's an impressive improvement: 24 characters. (There was a stray "$m" in your code, so it's even shorter than it looked at first.) I wasn't sure if reporting the result with an exit code was allowed in general but I took the "write a program (or function)" as allowing one to be flexible with how the result is returned in this particular case; exit codes are practically the function return values of the *nix environment. :-)
– nwk
Jan 8, 2017 at 22:38
• Make that 26 characters.
– nwk
Jan 8, 2017 at 22:44
• Actually, I'm decrementing $. at the end to avoid using twice $.-1 (especially since the first time it was ($.-1)/2 so it needed some extra parenthesis), so the $m in $m${m}{$.} isn't a mistake. Also, I just realized now, but the regexs should be surrounded with ^...$ (so extra character at the end or the beginning make them fail), or shorter: use ne instead of !~.
Jan 9, 2017 at 6:57
}})(
(M( {${w=(r-3)/2}})M\\5M ){${w}}))\\1\\2$)  Edit: Whoa, Arnauld saved me 6 bytes by changing s.split\n.length to s.search\n! Thanks! This takes a single multiline string and constructs a RegExp-based validation using the length of the input string. Returns true or false. Assumes a valid window has does not have a trailing newline. # Demo f=s=>!s.splitS.joinM.search(^((M{${r=s.search
}})(
(M( {${w=(r-3)/2}})M\\5M ){${w}}))\\1\\2\$);
MMM
MMM
MMM

SMSMS
M M M
SMSMS
M M M
SMSMS

MMMMMMM
M  S  M
M  S  M
MSSSSSM
M  S  M
M  S  M
MMMMMMM

Hello, World!

MMMM
MSSM
MS M
MMMM

MMSMM
M S.M
sSSSS
M S M
MMSMM

MMMMMMM
M  M  M
MMMMMMM
M  M  M
MMMMMMM

MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM.split

.forEach(test=>{console.log(test,f(test));});

• Nice approach! Could you use r=s.search('\n') instead of split / length? Jan 9, 2017 at 7:58
• @Arnauld awesome suggestion, thanks! Jan 9, 2017 at 8:59
• The parenthesys on s=>!s.splitS.joinM.search([...]) can be removed, without causing syntax errors. Jan 9, 2017 at 11:50
• @IsmaelMiguel correct, but then the string gets passed as a template, which invalidates the implicit RegExp Jan 9, 2017 at 12:37
• That sucks... I really wasnt expecting that... Jan 9, 2017 at 13:19