Convert a Forsyth-Edwards Notation string to ASCII art

9

0

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| |

EMBLEM

Posted 2016-04-23T20:46:04.220

Reputation: 2 179

Is it acceptable to write a function which takes input and returns output, rather than writing it to STDOUT? – Fund Monica's Lawsuit – 2016-04-24T19:09:39.877

@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. – Alex A. – 2016-04-24T21:19:20.443

2The answer you accepted is not the shortest one. Regardless of your feelings towards golfing languages, code golf means that the shortest code wins. – Dennis – 2016-04-25T00:21:25.427

@Dennis If I cannot exclude golfing languages from the challenge outright, I will exclude them from the winner's circle. Users may vote for the golfing language winner as they please and scroll down to see it.

– EMBLEM – 2016-04-25T01:16:28.593

3

You also cannot penalize them or accept an arbitrary answer. The whole site is built around objective winning criteria.

– Dennis – 2016-04-25T01:33:52.013

@Dennis I have removed my votes from all answers from which I could remove them and removed the accept of the Perl answer. I will not, however, accept an answer to this question unless that is also required. – EMBLEM – 2016-04-25T01:59:16.673

No, that's not required. Many challenges do not have accepted answers. – Dennis – 2016-04-25T04:38:43.903

1+1 for an interesting challenge. -2 for accepting the wrong answer for no good reason – James – 2016-06-20T19:15:51.453

Answers

9

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"

Ton Hospel

Posted 2016-04-23T20:46:04.220

Reputation: 14 114

1Missing shebang? – user253751 – 2016-04-25T02:27:53.457

15

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.

Downgoat

Posted 2016-04-23T20:46:04.220

Reputation: 27 116

1You can even do it all in ASCII for the same byte count with S\/` as the second stage. – Martin Ender – 2016-04-23T22:53:28.923

12

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.

Fund Monica's Lawsuit

Posted 2016-04-23T20:46:04.220

Reputation: 564

2You 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). – Ventero – 2016-04-24T08:30:24.333

5

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

Maltysen

Posted 2016-04-23T20:46:04.220

Reputation: 25 023

4

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.

Conor O'Brien

Posted 2016-04-23T20:46:04.220

Reputation: 36 228

Don't you mean ES6? ES7 isn't out yet I believe. – ericw31415 – 2016-04-23T22:40:05.080

@ericw31415 It isn't out, you're correct, but some browsers have started implementing part of the ES7 spec. – Conor O'Brien – 2016-04-23T22:40:33.833

Oh OK. But still your code doesn't use any of the ES7 features, right? – ericw31415 – 2016-04-23T22:42:47.690

1@ericw31415 Actually, it does. Array comprehensions ([x for(x of a)]) are ES7. – Conor O'Brien – 2016-04-23T22:43:22.537

Weren't Array Comprehensions removed from the spec, MDN says they were

– MayorMonty – 2016-04-25T03:01:18.067

@SpeedyNinja At the bottom it appears to be that they are still planned forbES7, though this current version of compressions is being deprecated in favor of the for of before the expression. shudders – Conor O'Brien – 2016-04-25T11:17:51.733

4

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

Leaky Nun

Posted 2016-04-23T20:46:04.220

Reputation: 45 011

3

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!

Alex A.

Posted 2016-04-23T20:46:04.220

Reputation: 23 761

Yay I inspired things :D – Fund Monica's Lawsuit – 2016-05-08T23:12:09.340

@QPaysTaxes You did indeed. Nice solution! – Alex A. – 2016-05-08T23:26:21.400

2

05AB1E, 21 bytes

Code:

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

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

Uses CP-1252 encoding. Try it online!.

Adnan

Posted 2016-04-23T20:46:04.220

Reputation: 41 965

2

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.

Neil

Posted 2016-04-23T20:46:04.220

Reputation: 95 035

Would /[\d/]/g,c=>+c?\ `.repeat(c):`\n`` work? – user81655 – 2016-04-24T04:36:09.177

1@user81655 Thanks, but I didn't like your emoticon, so I replaced it with an annoyed face with glasses. – Neil – 2016-04-24T08:50:12.467

1

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!

Adnan

Posted 2016-04-23T20:46:04.220

Reputation: 41 965

Try using the $* functionality :) – Leaky Nun – 2016-04-23T22:45:11.267

1

Jolf, 28 bytes

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

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

Conor O'Brien

Posted 2016-04-23T20:46:04.220

Reputation: 36 228

1

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)

R. Kap

Posted 2016-04-23T20:46:04.220

Reputation: 4 730

1

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;
}

Khaled.K

Posted 2016-04-23T20:46:04.220

Reputation: 1 435

1

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>

edc65

Posted 2016-04-23T20:46:04.220

Reputation: 31 086

1

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

Katenkyo

Posted 2016-04-23T20:46:04.220

Reputation: 2 857

print((...):gsub(".",function(c)return(c:find("%d")and("| "):rep(c)or c=="/"and"|\n"or"|"..c)end).."|") – Leaky Nun – 2016-07-18T09:56:07.893

print((...):gsub("%d",function(c)return("| "):rep(c)end):gsub("/","|\n"):gsub("([^%d%s|])","|%1").."|") for the same byte-count. – Leaky Nun – 2016-07-18T09:58:32.830

print((...):gsub("%d",function(c)return("| "):rep(c)end):gsub("([^%d |])","|%1"):gsub("/","\n").."|") is 101 bytes – Leaky Nun – 2016-07-18T10:05:46.283

1

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)

}

Flounderer

Posted 2016-04-23T20:46:04.220

Reputation: 596

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.

– Dennis – 2016-04-26T23:49:52.113

Technically, it is golfed. Just not very well. – Flounderer – 2016-04-27T01:32:54.847

0

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+"|";
}

Khaled.K

Posted 2016-04-23T20:46:04.220

Reputation: 1 435

You can save a couple of bytes be using integers instead of char literals in the comparisons – Blue – 2016-04-24T11:42:04.477

@Blue notes taken – Khaled.K – 2016-04-24T12:18:33.083

0

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

viercc

Posted 2016-04-23T20:46:04.220

Reputation: 101

0

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!

Blue

Posted 2016-04-23T20:46:04.220

Reputation: 26 661

0

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

Blue

Posted 2016-04-23T20:46:04.220

Reputation: 26 661

0

><>, 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. ¯\_(ツ)_/¯

Sok

Posted 2016-04-23T20:46:04.220

Reputation: 5 592