Play a valid chess move, given a board on stdin

11

1

The program plays white.

Example stdin:

8 ║♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 ║♟ ♟ ♟ ♟ … ♟ ♟ ♟
6 ║… … … … … … … …
5 ║… … … … ♟ … … …
4 ║… … … … … … … …
3 ║… … ♘ … … … … …
2 ║♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 ║♖ … ♗ ♕ ♔ ♗ ♘ ♖
——╚═══════════════
—— a b c d e f g h

Example stdout:

8 ║♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 ║♟ ♟ ♟ ♟ … ♟ ♟ ♟
6 ║… … … … … … … …
5 ║… … … … ♟ … … …
4 ║… … … … ♙ … … …
3 ║… … ♘ … … … … …
2 ║♙ ♙ ♙ ♙ … ♙ ♙ ♙
1 ║♖ … ♗ ♕ ♔ ♗ ♘ ♖
——╚═══════════════
—— a b c d e f g h

Any valid move is ok. "En passant" and castling are ignored. It is ok to show error messages or print nothing if there is no valid move.

The answer with the most votes wins.

Hristo Hristov

Posted 2012-01-21T13:54:52.263

Reputation: 343

@CMP, a triple repetition is irrelevant here, because it's legal to do the move that leads to it, and also legal to ignore it and keep playing (it's a draw only if a player calls it). – ugoren – 2012-02-05T08:48:29.597

though it's not mine, and it doesn't use stdin as input! but this is one of the best ones out there! it's a chess game with AI which can beat average human level just in 1023 bytes!

– Ali1S232 – 2012-01-23T07:56:53.710

I mean a standard error message caused by failure of some built-in function of the language. So that's ok? — Is it mandatory that the program can do any legal move? Perhaps castling and pawn special moves should be made optional with some bonus? – ceased to turn counterclockwis – 2012-01-21T15:12:58.990

2@leftaroundabout: Whenever you can castle, you can just move the rook instead, so you can skip the logic for that at least. – hammar – 2012-01-21T15:15:17.153

2... and thinking about it some more, the "en passant" move requires information about what previous moves has been made, which cannot be inferred from just the positions of the pieces, so I guess it might be safe to drop that. However, whether the double first move is available can be inferred from the rank of the pawn, so you might want to include that. – hammar – 2012-01-21T18:41:09.123

@hammar: you're right, I hadn't thought about that. The double move is also not important, except for one case: when you can go two steps you can also go one, so it only becomes important when you're in check and the double-move is the only move that covers the king. Also, even when you don't need to be able to use every move, you still need to consider that black can answer with any possibility. – ceased to turn counterclockwis – 2012-01-21T19:14:06.583

@hammar "en passant" and castling have to be ignored, there is no such thing as double move in chess as far as I know. – Hristo Hristov – 2012-01-21T20:36:35.737

@HristoHristov: I meant the way you can move a pawn 2 steps the first time. Not sure what the proper term is. – hammar – 2012-01-21T20:46:40.430

@hammar Now I understand. These are two possible moves of the pawn which can be valid or not, so they have to be considered. – Hristo Hristov – 2012-01-21T20:52:27.737

9Is resigning counted as a legal move? :) – gnibbler – 2012-01-22T13:19:59.910

@gnibbler resigning is legal, but it's not a "move" :) – Hristo Hristov – 2012-01-22T15:24:15.387

Can we assume we're not in check (without testing for that)? And don't have to test whether a move would uncover check? – smci – 2012-01-23T11:51:33.420

@smci No, these assumes are not acceptable. – Hristo Hristov – 2012-01-23T12:25:49.037

If you give the move in a standard format like FEN, you have the en passant target encoded, as well as castling availability. The only thing it can't do is a draw if the same position is seen 3 times. You need full history for that. – captncraig – 2012-01-23T15:52:42.180

Answers

16

I'm not complaining about upvotes, but to be fair... my solution here isn't actually all that great. Ugoren's is better, apart from lacking unicode support. Be sure to look at all the answers before voting, if you've come across this question only now!
Anyway.

Haskell, 893 888 904 952 (without castling)

862 (without pawn double-moves)

(You didn't specify if this is supposed to be code golf, but it seems to me it should)

χ=w⋈b;w="♙♢♤♔♕♖♗♘";b="♟♦♠♚♛♜♝♞"
μ=t⤀ζ++((\(x,y)->(x,-y))⤀)⤀μ;q c|((_,m):_)<-((==c).fst)☂(χ⋎μ)=m
t(x:y:l)=(d x,d y):t l;t _=[];d c=fromEnum c-78
ζ=["NM","NL","MMOM","MMMNMONMNOOMONOO",σ⋈δ,σ,δ,"MLOLPMPOOPMPLOLM"]
σ=l>>=(\c->'N':c:c:"N");δ=[l⋎l,reverse l⋎l]>>=(>>=(\(l,r)->[l,r]))
l="GHIJKLMOPQRSTU"
α c|c∊"♢♤"='♙'|c∊"♦♠"='♟'|c∊χ=c;π('♙':_)=6;π _=1
(⋎)=zip;(⤀)=map;(∊)=elem;(✄)=splitAt;(☂)=filter;(⋈)=(++)
φ r@(x,y)p a
 |x>7=φ(0,y+1)p a
 |y>7=[]
 |c<-a✠r=(c⌥r)p a y⋈φ(x+1,y)p a
(c⌥r)p a y
 |c==p!!0=(a☈r)c χ++const(y==π p)☂(a☈r)(p!!1)χ++(a☈r)(p!!2)('…':w)
 |c∊p=(a☈r)c χ
 |True=[]
a✠(x,y)=a!!y!!(x*2);o(x,y)=x>=0&&x<8&&y>=0&&y<8
(n➴a)(x,y)|(u,m:d)<-y✄a,(l,_:r)<-(x*2)✄m=u⋈(l⋈(n:r):d)
(a☈r@(x,y))c b=(α c➴('…'➴a)r)⤀((\r->o r&&not((a✠r)∊b))☂((\(ξ,υ)->(x+ξ,y+υ))⤀q c))
main=interact$unlines.uncurry((⋈).zipWith((⋈).(:" ║"))['8','7'..]
 .head.((all(any('♔'∊)).φ(0,0)b)☂).φ(0,0)w.(drop 3⤀)).(8✄).lines

When you have GHC installed (for instance as part of the Haskell platform) you can do just

$ runhaskell def0.hs < examplechessboard.txt
8 ║♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 ║♟ ♟ ♟ ♟ … ♟ ♟ ♟
6 ║… … … … … … … …
5 ║… ♘ … … ♟ … … …
4 ║… … … … … … … …
3 ║… … … … … … … …
2 ║♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 ║♖ … ♗ ♕ ♔ ♗ ♘ ♖
——╚═══════════════
—— a b c d e f g h

ceased to turn counterclockwis

Posted 2012-01-21T13:54:52.263

Reputation: 5 200

Now this is crazy :) I will check it out :) – Hristo Hristov – 2012-01-21T20:37:24.953

Any idea how to test this awesomeness? Ideone.com can not handle it... – Hristo Hristov – 2012-01-21T20:53:12.370

@HristoHristov: strange it doesn't work on Ideone. Probably has to do with the non-ASCII characters. – ceased to turn counterclockwis – 2012-01-21T21:10:41.527

yes, this is the problem with ideone – Hristo Hristov – 2012-01-21T21:12:50.293

Your example does not show a valid move, or do I miss something? – Hristo Hristov – 2012-01-21T21:20:42.170

@HristoHristov: right, I messed up the order of the list ζ when I added double pawn starting-moves. Fixed now. – ceased to turn counterclockwis – 2012-01-21T21:33:33.250

@leftaroundabout it's probably not about non-ASCII because it's created by Poles and all page must be in UTF-8 (i.e. "Zażółć gęślą jaźń"). – Hauleth – 2012-01-22T00:03:06.470

This is mad. I may have to learn Haskell. – Mr.Wizard – 2012-01-22T10:12:30.123

@Hauleth I don't know... UTF-8 is just right for GHC, so I would have though it should work, but as soon as I put any non-ASCII characters into a program (as a variable name or string literal doesn't seem to matter) it refuses to compile. — Codepad appears to be somewhat more tolerant, but it doesn't work there, either. – ceased to turn counterclockwis – 2012-01-22T13:25:14.707

@leftroundabout: Ideone uses GHC 6.8, which is quite old. It should work with a 7.x version. – hammar – 2012-01-22T15:05:14.340

14Congratulations, you've managed to make Haskell look like APL. :-) – Ilmari Karonen – 2012-01-22T15:54:44.697

11

C, 734 672 640 characters

Characters counted without removable whitespace.
The file format I used is not as requested, but simplified ASCII.
I need to add Unicode character support, it would cost some characets.

char*r=" kpnbrq  KPNBRQ $ ,&)$wxy()879()8(6:GI(",B[256],*b=B,i;
e(x,d,m,V,c,r,n,p){
    for(r=0,p=b[x];m/++r;){
        n=x+d*r;
        if(p==2+8*(d<0)||n&136||!(b[n]?r=8,8^p^b[n]^8&&c&65^64:c&65^65)
            ? r=m,0
            : V?v(n,x):b[n]==1)
            return b[x]=0,b[n]=p%8-2||n/16%7?p:p+4;
    }
    return d>0&&e(x,-d,m,V,c);
}
d(x,v,m,i)char*m;{
    return(i=*m-40)?e(x,i%64,b[x]%8-2?b[x]&4?7:1:(x/16-1)%5|i%2?1:2,v,i)||d(x,v,m+1):0;
}
v(t,f){
    bcopy(B,b+=128,128);
    b[t]=b[f];b[f]=0;
    i=a(1,63);
    b=B;
    return!i;
}
a(c,n){
    return b[i=n*2-n%8]&&b[i]/8==c&&d(i,!c,r+r[b[i]%8+15]-10)||n--&&a(c,n);
}
main(){
    for(;gets(b);b+=8)for(;*b;b++)*b=strchr(r,*b)-r;b=B;
    for(i=64*!a(0,63);i<64;i++%8-7||puts(""))putchar(r[b[i*2-i%8]]);
}

Input/output file format:
Must be exactly 8 lines of exactly 8 characters. pnbrqk are used for white pieces, PNBRQK for black pieces, spaces for spaces:

RNBQKBNR
PPPP PPP

 n  P


pppppppp
r bqkbnr

The logic is quite simple:
For each possible move of each white piece, try each possible move of each black piece.
If no black move captures the white king, the white move is valid.

The board is maintained as char[256], treated as a 16x16 matrix, where only the top-left 8x8 is used. Positions and movement vectors are kept in 8-bit integers (x:4,y:4). The extra bit allows using simple arithmetic (new_pos = old_pos + steps*direction), with easy detection of the board edge (&0x88 does the magic). r[] encodes three things:

  1. The first 15 bytes map internal piece codes (K=1,P=2,N=3,B=4,R=5,Q=6) to letters.
  2. The next 6 bytes map internal piece codes to offsets in the last part (K and Q are the same, B is their tail).
  3. The last 16 bytes encode the movement of all pieces, as '('+vector.

Functions:

  1. main reads the board, converts letters to internal code, calls a to find white moves, prints the board.
  2. arecursively loops over the 64 squares. For each piece of the right color (parameter c), it finds the movement rule for the piece and calls d.
  3. d recursively loops over the encoded movement rule, which is a list of vectors, calling e for each one. It gives e the original position, the vector and the range limit (7 for pieces above B, 2 for second rank pawns, 1 otherwise).
  4. e tests all movements along a vector. If the move is possible (i.e. pawns move forward, within board, not blocked, pawn capture diagonally), checks one of two things. For white moves, runs v to validate the move. For black moves, checks if the white king is captured. If true, the move is played on the board.
  5. v validates a white move. It copies the board aside, executes the move to test, and calls a again, to look for black moves.

ugoren

Posted 2012-01-21T13:54:52.263

Reputation: 16 527

At last, a solution with proper compressed encoding of the possible moves! And it's nicely fast. Don't you think you could add a Unicode wrapper and still be shorter than my code? – ceased to turn counterclockwis – 2012-02-08T18:08:44.130

@leftaroundabout, I guess I can. The main problem is that I'm working in a Linux command line, where you can't see Unicode, so debugging it would be annoying. I also have a version that saves about 40 more bytes (I'll update soon), so I have lots of chars to work with. – ugoren – 2012-02-08T19:14:05.213

@ugoren: Surely any half-way modern Linux distribution supports UTF-8 out of the box? – han – 2012-02-25T09:29:43.973

@han, I'm working on Windows and connect to Linux by SSH, and Unicode doesn't work. I can write to a file and open in in Windows, but it just isn't interesting any more. – ugoren – 2012-02-25T12:38:03.103

Will this compile with gcc? I'm using Geany for Windows with MinGW and it will compile with a bunch of errors and warnings but it will not build/run.eg:C:\Users\xxx\AppData\Local\Temp\ccpBG9zy.o:codegolfchess.c:(.text+0x2d8): undefined reference to `bcopy' collect2: ld returned 1 exit status – rpd – 2012-11-08T19:11:04.717

@rpd, It's meant for gcc/Linux. Try replacing bcopy(x,y,l) with memcpy(y,x,l). – ugoren – 2012-11-08T19:13:03.233

5

Python 2.6, 886 - 1425 characters

My initial version (in the revisions) came in at 886 characters but did not satisfy the spec completely (it did not check for avoiding checkmate ; it didn't even consider the possible moves of the black pieces).

Now it does (and I've fixed several bugs in the original). Alas this comes with a cost in characters: 1425 for now, but there should still be little room for improvement. This version should be a lot more solid in handling edge cases then the previous one.

#-*-coding:utf8-*-
import sys;e=enumerate
B,W=["♟","♜","♞","♝","♛","♚"],["♙","♖","♘","♗","♕","♔"]
R={"♙":[11,42],"♖":[28],"♘":[31],"♗":[8],"♕":[8,28],"♔":[1,21]}
def F(w):return sum([[(i,j)for j,p in e(o)if p==w]for i,o in e(Z)],[])
def G(x,y):
 P=Z[x][y];D=P in W;L=[]
 for o in R[P]if D else R[unichr(ord(P.decode('utf8'))-6).encode('utf8')]:
  r,k="%02d"%o        
  for g,h in[[(-1,-1),(1,1),(-1,1),(1,-1)],[[(1,-1),(1,1)],[(-1,-1),(-1,1)]][D],[(-1,0),(1,0),(0,-1),(0,1)],[(-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1)],[(-1,0)]][int(r)]:
   J=0
   for i in range(int(k)):
    T=x+(i+1)*g;U=y+(i+1)*h
    if T<0 or T>7 or U<0 or U>7:break
    M=Z[T][U]
    if not J:L.append((T,U,P,M))
    else:break
    if r in"02"and(M in W+B):
     J=1
     if not((D and M in B)or(not D and M in W)):L.pop()
    elif(r=="1"and not((D and M in B)or(not D and M in W)))or(r=="4"and((i==1 and x!=6)or M!="…")):L.pop()
 return L  
Z=[[y for y in l[5:].split()]for l in sys.stdin.readlines()[:-2]]
Q=[]
for p in R:
 for i,j in F(p):
  for M,L,c,_ in G(i,j):
   O=Z[M][L];Z[i][j]="…";Z[M][L]=c;E=[];map(E.extend,map(F,B))
   if not any(any(1 for _,_,_,I in G(v,h)if I==["♔","♚"][c in B])for v,h in E):Q.append((i,j,M,L,c))
   Z[i][j]=c;Z[M][L]=O
(x,y,X,Y,p)=Q[0];Z[x][y]="…";Z[X][Y]=p
for i,h in e(Z):print`8-i`+' ║'+' '.join(h)
print"——╚"+"═"*16+"\n—— a b c d e f g h"

Example input and output:

# INPUT

8 ║♜ ♞ ♝ … ♚ ♝ ♞ ♜
7 ║♟ ♟ ♟ ♟ … ♟ ♟ ♟
6 ║… … … … … … … …
5 ║… … … … ♟ … … …
4 ║… … … … … … ♙ ♛
3 ║… … … … … ♙ … …
2 ║♙ ♙ ♙ ♙ ♙ … ♙ …
1 ║♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
——╚═══════════════
—— a b c d e f g h
# OUTPUT

8 ║♜ ♞ ♝ … ♚ ♝ ♞ ♜
7 ║♟ ♟ ♟ ♟ … ♟ ♟ ♟
6 ║… … … … … … … …
5 ║… … … … ♟ … … …
4 ║… … … … … … ♙ ♛
3 ║… … … … … ♙ ♙ …
2 ║♙ ♙ ♙ ♙ ♙ … … …
1 ║♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
——╚════════════════
—— a b c d e f g h

ChristopheD

Posted 2012-01-21T13:54:52.263

Reputation: 1 599

It's 886 bytes, but only 854 chars. (My program has over 1kB, thanks to the many non-ASCII operators!) — Are you going to add checking for taking the king yet? – ceased to turn counterclockwis – 2012-02-08T17:58:06.653

@leftaroundabout: I've added the king checks (which forces me to also account for possible moves of black, and adds a lot of characters...). Oh well, this version should be more solid around edge cases (as far as I tested). – ChristopheD – 2012-02-15T23:31:55.037