What are my chess moves?
JavaScript (ES6), 256 ... 229 227 bytes
Takes input as two strings (p, s)
, where p
is the case-insensitive name of the piece and s
is the source square. Returns a space-separated list of destination squares.
f=(p,s,i=128)=>i--?f(p,s,i)+([x=255,x<<8,2,x,195,60][P=(I=parseInt)(p,32)*5&7]>>i/8&((d=i%8)<(S=(x=I(s,18)-181)/18|x%18*16,P>2?8:P<2|S>23||2))&!((S+=I('gfh1eivx'[i>>4],36)*(i&8?d+1:~d))&136)?'abcdefgh'[S&7]+(S+16>>4)+' ':''):''
How it works
Piece name decoding
The name of the piece p
is converted to an integer P
by parsing it in base 32, multiplying the result by 5 and isolating the 3 least significant bits: parseInt(p, 32) * 5 & 7
Piece | As base 32 | * 5 | & 7
----------+------------+------------+----
"king" | 674544 | 3372720 | 0
"knight" | 695812669 | 3479063345 | 1
"pawn" | 810 | 4050 | 2 NB: 'pawn' is parsed as 'pa', because 'w'
"queen" | 28260823 | 141304115 | 3 is not a valid character in base 32
"rook" | 910100 | 4550500 | 4
"bishop" | 388908825 | 1944544125 | 5
In addition to being a perfect hash, a good thing about this formula is that all sliding pieces (Queen, Rook and Bishop) appear at the end of the list, which allows to identify them with a simple P > 2
.
Source square decoding
The source square s
is converted to an integer S
by parsing it in base 18 and converting the result to 0x88 coordinates.
That leads to the following board representation:
a b c d e f g h
8 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77
7 0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67
6 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57
5 0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47
4 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37
3 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27
2 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17
1 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
Directions encoding
There are 16 possible directions: 4 along ranks and files, 4 along diagonals and 8 for the knights.
As hexadecimal: As decimal:
+0x1F +0x21 +31 +33
+0x0E +0x0F +0x10 +0x11 +0x12 +14 +15 +16 +17 +18
-0x01 [SRC] +0x01 -1 [SRC] +1
-0x12 -0x11 -0x10 -0x0F -0x0E -18 -17 -16 -15 -14
-0x21 -0x1F -33 -31
Therefore, the list of directions that a piece can move towards can be encoded as a 16-bit bitmask.
Bit: | F E D C B A 9 8 7 6 5 4 3 2 1 0 | As
Vector: | +33 -33 +31 -31 +18 -18 +14 -14 +1 -1 +17 -17 +15 -15 +16 -16 | decimal
--------+-----------------------------------------------------------------+--------
King | 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 | 255
Knight | 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 | 65280
Pawn | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 | 2
Queen | 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 | 255
Rook | 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 | 195
Bishop | 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 | 60
The absolute values of the direction vectors can be stored in base 36:
[16, 15, 17, 1, 14, 18, 31, 33][n] <==> parseInt('gfh1eivx'[n], 36)
Formatted and commented
f = (p, s, i = 128) => // given a piece p, a square s and a counter i
i-- ? // if i is >= 0:
f(p, s, i) + ( // do a recursive call to f()
[ x = 255, x << 8, 2, x, 195, 60 ][ // take the direction bitmask for:
P = (I = parseInt)(p, 32) * 5 & 7 // P = piece index in [0 .. 5]
] >> i / 8 & ( // and test the bit for the current direction
(d = i % 8) < ( // d = current move distance - 1
S = (x = I(s, 18) - 181) / 18 | // S = source square in
x % 18 * 16, // 0x88 representation
P > 2 ? 8 : P < 2 | S > 23 || 2 // compare d with the maximum move range for
) // this piece
) ? // if the above test passes:
( // we add to S:
S += I( // the absolute value of the vector of
'gfh1eivx'[i >> 4], 36 // the current direction,
) * // multiplied by
(i & 8 ? d + 1 : ~d) // the move distance with the vector sign
) & 136 ? // if the result AND'd with 0x88 is not null:
'' // this is an invalid destination square
: // else:
'abcdefgh'[S & 7] + // it is valid: convert it back to ASCII
(S + 16 >> 4) + ' ' // and append a space separator
: // else:
'' // this is an invalid move
) // end of this iteration
: // else:
'' // stop recursion
Test cases
f=(p,s,i=128)=>i--?f(p,s,i)+([x=255,x<<8,2,x,195,60][P=(I=parseInt)(p,32)*5&7]>>i/8&((d=i%8)<(S=(x=I(s,18)-181)/18|x%18*16,P>2?8:P<2|S>23||2))&!((S+=I('gfh1eivx'[i>>4],36)*(i&8?d+1:~d))&136)?'abcdefgh'[S&7]+(S+16>>4)+' ':''):''
console.log(f('Rook', 'a8'));
console.log(f('Pawn', 'a2'));
console.log(f('Pawn', 'a3'));
console.log(f('Knight', 'a1'));
console.log(f('Bishop', 'h1'));
console.log(f('King', 'b2'));
console.log(f('Queen', 'h1'));
Python 2, 484 472 423 406 bytes
This is a lot longer than I wanted it to be.
i=input()
a=ord(i[1][0])-97
c=int(i[1][1])
g=range(-8,8)
b=sum([[(a-x,c-x),(a-x,c+x)]for x in g],[])
r=sum([[(a-x,c),(a,c-x)]for x in g],[])
print[chr(x[0]+97)+str(x[1])for x in set([[(a,c+1)]+[(a,c+2)]*(c==2),b,sum([[(a-x,c-y)for x,y in[[X,Y],[X,-Y],[-X,Y],[-X,-Y]]]for X,Y in[[1,2],[2,1]]],[]),r,b+r,[(a-x,c-y)for x in[-1,0,1]for y in[-1,0,1]]]['wsioen'.find(i[0][2])])-set([a,c])if-1<x[0]<8and 0<x[1]<9]
-12 bytes by removing some unnecessary variable names
-49 bytes thanks to ovs by splitting s into multiple variables
-17 bytes thanks to ovs by switching from range to comparison
Input is taken as ['PieceName', '<letter><num>']
. Output is given as an array of strings in the same 1-indexed a-h format <letter><num>
.