12
\$\begingroup\$

In chess, Forsyth-Edwards Notation, more commonly called "FEN", is a textual way of transcribing boards. It describes each of the board's eight rows (called "ranks" in chess) from top to bottom from White's perspective. The pieces are written as K (king), Q (queen), R (rook), B (bishop), N (knight), and P (pawn). Black pieces use these letters in lowercase, and white pieces use these letters in uppercase. Empty spaces are indicated by a number from 1 to 8 indicating how many consecutive empty spaces there are. A completely empty rank would be 8, a single black rook in the rightmost column (called "files" in chess) would be 7r, and two white pawns on each end of a row would be PP4PP. Ranks are separated by a /. There is normally other information added, indicating which side is to move, castling and en passant rights, move number, and halfmove clock, but we will ignore them for the purposes of this challenge.

Input

A FEN string, from the command line or STDIN, as you please. You may assume that this string is always valid.

Output

Write to STDOUT a simple ASCII art representation of the board as it would actually appear:

  • Pieces are represented by their character in FEN
  • Empty squares are represented by spaces
  • Pieces and squares are separated by a pipe | and there are pipes on each side of the board

So an empty board, written as 8/8/8/8/8/8/8/8 in FEN, would appear as

| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |

The starting position of a chess game is written as rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR, and would appear as

|r|n|b|q|k|b|n|r|
|p|p|p|p|p|p|p|p|
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
|P|P|P|P|P|P|P|P|
|R|N|B|Q|K|B|N|R|

The final position of Anderssen-Kieseritzky 1851, called "The Immortal Game" in the chess community, is written as r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1, and your program when fed that input would output:

|r| |b|k| | | |r|
|p| | |p|B|p|N|p|
|n| | | | |n| | |
| |p| |N|P| | |P|
| | | | | | |P| |
| | | |P| | | | |
|P| |P| |K| | | |
|q| | | | | |b| |
\$\endgroup\$
8
  • \$\begingroup\$ Is it acceptable to write a function which takes input and returns output, rather than writing it to STDOUT? \$\endgroup\$
    – anon
    Apr 24, 2016 at 19:09
  • \$\begingroup\$ @QPaysTaxes By default we allow it and indeed several solutions do that already. Ultimately it's up to the OP though it seems unnecessary to override our defaults in this case. \$\endgroup\$
    – Alex A.
    Apr 24, 2016 at 21:19
  • 2
    \$\begingroup\$ The answer you accepted is not the shortest one. Regardless of your feelings towards golfing languages, code golf means that the shortest code wins. \$\endgroup\$
    – Dennis
    Apr 25, 2016 at 0:21
  • 3
    \$\begingroup\$ You also cannot penalize them or accept an arbitrary answer. The whole site is built around objective winning criteria. \$\endgroup\$
    – Dennis
    Apr 25, 2016 at 1:33
  • 1
    \$\begingroup\$ +1 for an interesting challenge. -2 for accepting the wrong answer for no good reason \$\endgroup\$
    – DJMcMayhem
    Jun 20, 2016 at 19:15

23 Answers 23

16
\$\begingroup\$

Retina, 13 bytes

\d
$* 
/
¶

|

Try it online!

Explanation

The first part (note the trailing space):

\d
$* 

is to convert a to the specific number of spaces. Retina has a $* feature to repeat. The way it works is: <num>$*<char> if there is no <num>, Retina will assume $& or the matched string, in this case the matched number.

The next part:

/
¶

is pretty simple, it replaces all / with which is a newline.

The last part works the same:

    
|

This will replace everything (hence why there is nothing on the first line) with |. Putting a | everywhere.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can even do it all in ASCII for the same byte count with S`/ as the second stage. \$\endgroup\$ Apr 23, 2016 at 22:53
12
\$\begingroup\$

Ruby - 75 82 78 76 75 62 59 58 57 56 bytes

->n{"|#{n.gsub(/\d|
/){' '*$&.hex}.chars*?|}|".tr'/',$/}

Saved a few bytes thanks to Ventero

Let me explain (with \n replacing the literal newline):

->n{"...".tr'/',$/}

This implicitly returns the value of the string, with each / replaced with a newline (by default, $/ contains a newline)

"|#{...}|"

This is super simple; it's just a string containing a pipe, string interpolation, and another pipe. The string interpolation is evaluated

n.gsub(/\d|\n/){' '*$&.hex}...

This replaces every number with that many spaces. I can save a few bytes by also finding newlines here; because hex returns 0 if the string isn't a valid number, when it finds a newline –- i.e. the one at the end of the result of gets –- it replaces it with a 0-length string, effectively deleting it. Without this, there would be a trailing pipe.

$& is a magic variable which represents the complete text of the latest variable match, which lets me save a byte by eliminating |d|. I can save another byte by using .hex instead of .to_i, which works because every number is less than 9, which means that hex and decimal have the same values.

.chars*?|

This puts a pipe between every character. Note that this is what puts the pipes on either side of the lines (except the first and last) because the slashes, which eventually turn into newlines through tr, count as characters, and are therefore surrounded by pipes. The ?| just means "the one-character string "|"".

And... that's it. It's a frankly scandalously simple program. It just uses a lot of sneaky syntax tricks.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ You can save 4 more characters by applying a few simple tricks: puts"|#{gets.gsub(/\d|\n/){' '*$&.hex}.chars*?|}|".split'/' (of course, replace the \n with a literal newline again). \$\endgroup\$
    – Ventero
    Apr 24, 2016 at 8:30
11
\$\begingroup\$

Perl, 28 bytes

Includes +2 for -lp

Give input on STDIN

fen.pl <<< "r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1"

fen.pl:

#!/usr/bin/perl -lp
s/\d/$"x$&/eg;s/|/|/g;y;/;

Actually in the league of some golfing languages...

Notice that the file based version needs the final newline in the file so that one is really 29 bytes. But the commandline version doesn't need that extra newline and therefore the code counts as 28 bytes:

perl -lpe 's/\d/$"x$&/eg;s/|/|/g;y;/;' <<< "r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1"
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Missing shebang? \$\endgroup\$
    – user253751
    Apr 25, 2016 at 2:27
5
\$\begingroup\$

Pyth - 24 22 21 bytes

.i*\|72jcu:G`H*Hd9z\/

Test Suite.

+                     Concatenate
 K\|                  Store "|" in K and use value
+         K           Concatenate to end
 jK                   Join string by K, this puts "|" between each char
  :                   String substitution
        \/            Replace "/"
         b            With newline
   u                  Reduce
        9             Over [0, 9)
         z            With input as base case
    :G                String substitution current val
     `H               Replace stringifyed int from list we're looping through
     *Hd              With " "*that int
\$\endgroup\$
4
\$\begingroup\$

Pyth, 23 bytes

VT=:Q`N*dN;jc.i*\|72Q\/

Try it online!

How it works:

VT=:Q`N*dN;jc.i*\|72Q\/
VT        ;                for N in range(10):
  =:Q`N*dN                     Q = Q.replace(`N`,repeat(' ',N))
             .i*\|72Q      temp = interweave(repeat('|',72), Q)
            c        \/    temp = chop(temp,'/')
           j               temp = join(temp,'\n')
                           print temp
\$\endgroup\$
4
\$\begingroup\$

JavaScript ES7, 80 70 bytes

Is an anonymous function that accepts a string as input.

a=>[,...[+t?" ".repeat(t):t<"0"?`
`:t for(t of a)].join``,`
`].join`|`

80-byte ES6-only appraoch.

a=>a.split`/`.map(x=>[,...x.replace(/\d/g,t=>" ".repeat(t)),`
`].join`|`).join``

Explanation

We use an array comprehension to loop over the list:

[+t?" ".repeat(t):t<"0"?`
`:t for(t of a)]

This is equivalent to:

[!isNaN(parseInt(t, 10)) ? " ".repeat(parseInt(t, 10)) : t === "/" ? "\n" : t for(t of a)]

If it's a number, we have that number of spaces. If it's a /, we have a newline. Otherwise, we have the character. Then, we join the comprehension with nothing to make a string.

Then, we create an array of length 3 [,...that,"\n"]. ... splats the joined comprehension into chars. Joining this yields the result.

\$\endgroup\$
6
  • \$\begingroup\$ Don't you mean ES6? ES7 isn't out yet I believe. \$\endgroup\$
    – ericw31415
    Apr 23, 2016 at 22:40
  • \$\begingroup\$ @ericw31415 It isn't out, you're correct, but some browsers have started implementing part of the ES7 spec. \$\endgroup\$ Apr 23, 2016 at 22:40
  • \$\begingroup\$ Oh OK. But still your code doesn't use any of the ES7 features, right? \$\endgroup\$
    – ericw31415
    Apr 23, 2016 at 22:42
  • 1
    \$\begingroup\$ @ericw31415 Actually, it does. Array comprehensions ([x for(x of a)]) are ES7. \$\endgroup\$ Apr 23, 2016 at 22:43
  • \$\begingroup\$ Weren't Array Comprehensions removed from the spec, MDN says they were \$\endgroup\$
    – bren
    Apr 25, 2016 at 3:01
3
\$\begingroup\$

Julia, 62 bytes

s->split("|"join(replace(s,r"\d",d->" "^parse(d)),"|")"|","/")

This is an anonymous function that accepts a string and returns an array of strings. To call it, assign it to a variable.

The approach is the same as in QPaysTaxes' clever Ruby answer. We replace each digit in the input with that many spaces, place | between each character, tack | onto the front and back, and split into an array on /.

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ Yay I inspired things :D \$\endgroup\$
    – anon
    May 8, 2016 at 23:12
  • \$\begingroup\$ @QPaysTaxes You did indeed. Nice solution! \$\endgroup\$
    – Alex A.
    May 8, 2016 at 23:26
2
\$\begingroup\$

Retina, 50 45 bytes

This was fun haha. Not only am I a noob in Retina, but also in regex in general... This can probably be golfed a lot, so I'll do some more research.

Code:

8
44
7
34
6
42
5
 4
4
22
3
 2
2
  
1
 
/
¶

|

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ Try using the $* functionality :) \$\endgroup\$
    – Leaky Nun
    Apr 23, 2016 at 22:45
2
\$\begingroup\$

05AB1E, 21 bytes

Code:

9GNNð×:}S'|ý"|ÿ|"'/¶:

Also 21 bytes: '|¹9GNNð×:}S'|«JJ'/¶:.

Uses CP-1252 encoding. Try it online!.

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

Python 3.5, 112 bytes:

def r(o):print(''.join(['| '*8if h=='8'else'| '*int(h)if h.isdigit()else'|\n'if h=='/'else'|'+h for h in o])+'|')

Try it online! (Ideone)

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

JavaScript (ES6), 69 67 62 bytes

s=>[,...s.replace(/[/-8]/g,c=>+c?' '.repeat(c):`
`),,].join`|`

The extra commas create empty values in the outer split which creates the beginning and ending | characters. You need two trailing commas because trailing commas are optional at the end of lists, so the first one is still part of the previous item.

Edit: Saved 5 bytes thanks to @user81655.

\$\endgroup\$
2
  • \$\begingroup\$ Would /[\d/]/g,c=>+c?` `.repeat(c):`\n` work? \$\endgroup\$
    – user81655
    Apr 24, 2016 at 4:36
  • 1
    \$\begingroup\$ @user81655 Thanks, but I didn't like your emoticon, so I replaced it with an annoyed face with glasses. \$\endgroup\$
    – Neil
    Apr 24, 2016 at 8:50
2
\$\begingroup\$

R (out of competition)

Sorry if it's not appropriate to post this, but I thought it was cool that I just happened to have a function lying around that actually works for this question without editing! It prints unicode output rather than ascii, though. I can't remember quite why I wrote it, but it wasn't to answer a challenge.

function(x){
# x = FEN position, a string
# can be split with / or ,
# example: forsythe("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R")

allowed <- c(paste(1:64), 
c("k", "q", "r", "b", "n", "p", "K", "Q", "R", "B", "N", "P"))
chars <- strsplit(x, "")[[1]]
chars <- chars[-which(!(chars %in% allowed))]
out <- c()
for (i in 1:length(chars)){
  if (chars[i] %in% paste(1:64)){
    out <- c(out, rep(" ", as.numeric(chars[i])))
  }
  else{
    out <- c(out, chars[i])
  }
}
if (length(out) < 64) out <- c(out, rep(" ", 64-length(out)))

pieces <- strsplit("KQRBNPkqrbnp", "")[[1]]
unicode <- c("\u2654", "\u2655", "\u2656", 
"\u2657", "\u2658", "\u2659", "\u265A", "\u265B", 
"\u265C", "\u265D", "\u265E", "\u265F")

for (i in 1:64){
  if (out[i] %in% pieces){
    out[i] <- unicode[which(pieces==out[i])]
  }
  else{
  }
}
out <- matrix(out, nc=8, byrow=T)
#print(out)

plot(0, xlim=c(0, 8), ylim=c(0, 8), type="n", xaxt="n", yaxt="n",
xlab="", ylab="")
for (i in 0:7){ for (j in 0:7){ rect(i, j, i+1, j+1,
col=ifelse(((i+j) %% 2) == 0, grey(0.95), "white"), border=F) }}

for (i in 0:7){ for (j in 0:7){
  text(i+0.5, j+0.5, out[8-j, i+1], cex=2)  
}}

axis(1, labels=letters[1:8], at=1:8 - 0.5, tick=F)
axis(2, labels=paste(1:8), at=1:8-0.5, las=2, tick=F)

}
\$\endgroup\$
2
  • \$\begingroup\$ The rules outlined in our help center state that all solutions to challenges must be a serious contender for the winning criteria in use. For code golf, this means that all answers must be golfed. \$\endgroup\$
    – Dennis
    Apr 26, 2016 at 23:49
  • 1
    \$\begingroup\$ Technically, it is golfed. Just not very well. \$\endgroup\$
    – Flounderer
    Apr 27, 2016 at 1:32
1
\$\begingroup\$

Jolf, 28 bytes

RΜGi'/dΆγ'|RGρH«\d»d*♣PHEγγS

Replace with the character \x05, or try it here!

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

C, 252 bytes

i=-1,j,s=1,x;C(char*n){while(n[++i])s+=isdigit(n[i])?n[i]*2+1:2;char*m=(char*)malloc(s);for(i=j=-1;n[++i]&(m[++j]='|');)if(n[i]=='/')m[++j]='\n';else if(isdigit(n[i]))for(x=n[i]-'0';x;--x&&(m[++j]='|'))m[++j]=' ';else m[++j]=n[i];m[++j]='\0';return m;}

Detailed try online

// input-string, input-string-size
char* C(char*n)
{
    int i=-1,j,s=1,x;

    // figure out required grid size
    while(n[++i])s+=isdigit(n[i])?n[i]*2+1:2;
    char*m=(char*)malloc(s);

    i=j=-1;
    while(n[++i]) // while not end of string
    {
        m[++j]='|'; // seperator

        if (n[i]=='/') // end of row
            m[++j]='\n';
        else if (isdigit(n[i])) // fill spaces
            for(x=n[i]-'0';x;--x&&(m[++j]='|')) m[++j]=' ';
        else
            m[++j]=n[i]; // single literals
    }

    m[++j]='|';
    m[++j]='\0';
    return m;
}
\$\endgroup\$
1
\$\begingroup\$

Haskell, 110 Bytes

p '/'="\n"
p c|'1'<=c&&c<='8'=replicate(read[c])' '
p c=[c]
main=getLine>>=putStrLn.('|':).(>>=(:"|")).(>>=p)

Ungolfed:

p c | c=='/'           = "\n"
    | '1'<=c && c<='8' = replicate (read [c]) ' '
    | otherwise        = [c]
addPipes string = "|" ++ concatMap (\c -> [c] ++ "|") string
main = getLine >>= putStrLn . addPipes . concatMap p
\$\endgroup\$
1
\$\begingroup\$

JavaScript (FireFox 30+), 61

Using array comprehension that is not standard EcmaScript anymore

f=>'|'+[for(c of f)+c?' |'.repeat(c):c<'A'?`
|`:c+'|'].join``

Test

F=f=>'|'+[for(c of f)+c?' |'.repeat(c):c<'A'?`\n|`:c+'|'].join``

console.log=x=>O.textContent+=x+'\n'

;['8/8/8/8/8/8/8/8','rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR',
'r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1']
.forEach(t=>console.log(F(t)+'\n'))
<pre id=O></pre>

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

Python, 84 bytes

lambda a:"".join(c*c.isalpha()or"\n"*(c=="/")or" "*int(c)for c in a).replace("","|")

Explanation:

        c*c.isalpha()                                                       - if c is alphabetical, use c
                       "\n"*(c=="/")                                        - if it's "|", replace it with a newline
                                      " "*int(c)                            - else its an int.
"".join(                                                  ).replace("","|") - interweave "|" between the chars
\$\endgroup\$
1
\$\begingroup\$

Lua, 106 Bytes

print("|"..(...):gsub(".",function(c)return c:find("%d")and(" |"):rep(c)or c=="/"and"\n|"or c.."|"end),'')

Ungolfed

print("|"..                   -- prepend | to the following string
  (...):gsub(".",function(c)  -- iterate over each character in the argument
    return                    -- replaces in the argument
           c:find("%d")       -- if c is a number
             and(" |"):rep(c) --   replace by " |"*c
           or c=="/"          -- elseif c is a slash
             and"\n|"         -- replace by "\n|"
           or c.."|"          -- else (case letter)replace by c
  end)                        -- return the modified string
,'')                          -- add an empty parameter to print
                              -- it suppresses the second output of gsub
\$\endgroup\$
3
  • \$\begingroup\$ print((...):gsub(".",function(c)return(c:find("%d")and("| "):rep(c)or c=="/"and"|\n"or"|"..c)end).."|") \$\endgroup\$
    – Leaky Nun
    Jul 18, 2016 at 9:56
  • \$\begingroup\$ print((...):gsub("%d",function(c)return("| "):rep(c)end):gsub("/","|\n"):gsub("([^%d%s|])","|%1").."|") for the same byte-count. \$\endgroup\$
    – Leaky Nun
    Jul 18, 2016 at 9:58
  • \$\begingroup\$ print((...):gsub("%d",function(c)return("| "):rep(c)end):gsub("([^%d |])","|%1"):gsub("/","\n").."|") is 101 bytes \$\endgroup\$
    – Leaky Nun
    Jul 18, 2016 at 10:05
1
\$\begingroup\$

><>, 64 bytes

<v?(0:i
r\
"<o-*=@"%/":   v?*(@)@":/"::;?(0:o"|
 ^~?="0":-1o" "<

4 wasted bytes due to alignment issues, not sure how to golf them out though. ¯\_(ツ)_/¯

\$\endgroup\$
0
\$\begingroup\$

Java 7, 190 184 bytes

String Z(int c){String m="";if(c==47)m+="|\n";else if(c>57)m+="|"+c;else while(c-->48)m+="| ";return m;}String C(String n){String m="";for(char x:n.toCharArray())m+=Z(x);return m+"|";}

Detailed try online

public static String Z(char c)
{
    String m="";
    if(c=='/')m+="|\n";
    else if(c>'9')m+="|"+c;
    else while(c-->'0')m+="| ";
    return m;
}

public static String C(String n)
{
    String m="";
    for(char x:n.toCharArray())m+=Z(x);
    return m+"|";
}
\$\endgroup\$
2
  • \$\begingroup\$ You can save a couple of bytes be using integers instead of char literals in the comparisons \$\endgroup\$
    – Blue
    Apr 24, 2016 at 11:42
  • \$\begingroup\$ @Blue notes taken \$\endgroup\$
    – Khaled.K
    Apr 24, 2016 at 12:18
0
\$\begingroup\$

Pyke, 25 20 bytes

FD~u{RIbd*(s\/n:k\|:

Explanation:

F         (          -    for char in input:
 D~u{RI              -     if char in '0123456789': 
       bd*           -      char = " "*int(char)
           s         -   sum(^)
            \/n:     -  ^.replace("/","\n")
                k\|: - ^.replace("", "|")

Try it here!

\$\endgroup\$
0
\$\begingroup\$

brev, 89 bytes

(fn(print(strse x'num(->(m 0)string->number(make-string #\ ))"."(c conc "|")"/""\n")"|"))
\$\endgroup\$
0
\$\begingroup\$

Japt v2.0a0 -R, 15 bytes

r\dÈ°çÃrP'| q'/

Try it

r\dÈ°çÃrP'| q'/     :Implicit input of string U
r                   :Replace
 \d                 :  Regex /[0-9]/g
   È                :  Pass each match through a function
    °               :    Postfix increment (to convert from string to integer)
     ç              :    Repeat a space that many time
      Ã             :End replace
       r            :Replace
        P           :  Empty string
         '|         :  With "|"
            q'/     :Split on "/"
                    :Implicit output, joined with newlines
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.