Find Chess960 starting position identifier: Difference between revisions
m (→{{header|Phix}}: use filter odd/even method for D/L (as just noticed in Raku)) |
m (→{{header|Wren}}: Minor tidy) |
||
(68 intermediate revisions by 12 users not shown) | |||
Line 1: | Line 1: | ||
{{task}} |
{{task}} [[Category:Chess960]] |
||
As described on the '''[[Chess960]]''' |
As described on the '''[[Chess960]]''' page, Chess960 (a.k.a Fischer Random Chess, Chess9LX) is a variant of chess where the array of pieces behind the pawns is randomized at the start of the game to minimize the value of opening theory "book knowledge". That task is to generate legal starting positions, and some of the solutions accept a standard [[wp:Fischer random chess numbering scheme|Starting Position Identifier number]] ("SP-ID"), and generate the corresponding position. |
||
;Task: |
;Task: |
||
This task is to go the other way: given a starting array of pieces (provided in any form that suits your implementation, whether string or list or array, of letters or Unicode chess symbols or enum values, etc.), derive its unique SP-ID. For example, given the starting array '''QNRBBNKR''' (or '''♕♘♖♗♗♘♔♖''' or '''♛♞♜♝♝♞♚♜'''), your (sub)program should return 105; given the starting lineup of standard chess, it should return 518. |
This task is to go the other way: given a starting array of pieces (provided in any form that suits your implementation, whether string or list or array, of letters or Unicode chess symbols or enum values, etc.), derive its unique SP-ID. For example, given the starting array '''QNRBBNKR''' (or '''♕♘♖♗♗♘♔♖''' or '''♛♞♜♝♝♞♚♜'''), which we assume is given as seen from White's side of the board from left to right, your (sub)program should return 105; given the starting lineup of standard chess, it should return 518. |
||
You may assume the input is a valid Chess960 position; detecting invalid input (including illegal characters or starting arrays with the bishops on the same color square or the king not between the two rooks) is optional. |
You may assume the input is a valid Chess960 position; detecting invalid input (including illegal characters or starting arrays with the bishops on the same color square or the king not between the two rooks) is optional. |
||
Line 9: | Line 9: | ||
;Algorithm: |
;Algorithm: |
||
The derivation is the inverse of the algorithm given at [[wp:Fischer random chess numbering scheme#Direct_derivation|Wikipedia]], and goes like this (we'll use the standard chess setup as an example) |
The derivation is the inverse of the algorithm given at [[wp:Fischer random chess numbering scheme#Direct_derivation|Wikipedia]], and goes like this (we'll use the standard chess setup as an example). |
||
1. Ignoring the Queen and Bishops, find the positions of the Knights within the remaining five spaces (in the standard array they're in the second and fourth positions), and then find the index number of that combination. There's [[wp:Fischer random chess numbering scheme#Direct_derivation|a table]] at the above Wikipedia article, but it's just the possible positions sorted left to right and numbered 0 to 9: 0='''NN---''', 1='''N-N--''', 2='''N--N-''', 3='''N---N''', 4='''-NN--''', etc; our pair is combination number 5. Call this number N. '''N=5''' |
1. Ignoring the Queen and Bishops, find the positions of the Knights within the remaining five spaces (in the standard array they're in the second and fourth positions), and then find the index number of that combination. There's [[wp:Fischer random chess numbering scheme#Direct_derivation|a table]] at the above Wikipedia article, but it's just the possible positions sorted left to right and numbered 0 to 9: 0='''NN---''', 1='''N-N--''', 2='''N--N-''', 3='''N---N''', 4='''-NN--''', etc; our pair is combination number 5. Call this number N. '''N=5''' |
||
2. |
2. Still ignoring the Bishops, find the position of the Queen in the remaining 6 spaces; number them 0..5 from left to right and call the index of the Queen's position Q. In our example, '''Q=2'''. |
||
3. Finally, find the positions of the two bishops within their respective sets of four like-colored squares. It's important to note here that the board in chess is placed such that the leftmost position on the home row is on a dark square and the rightmost a light. So if we number the squares of each color 0..3 from left to right, the dark bishop in the standard position is on square 1 ('''D=1'''), and the light bishop is on square 2 ('''L=2'''). |
3. Finally, find the positions of the two bishops within their respective sets of four like-colored squares. It's important to note here that the board in chess is placed such that the leftmost position on the home row is on a dark square and the rightmost a light. So if we number the squares of each color 0..3 from left to right, the dark bishop in the standard position is on square 1 ('''D=1'''), and the light bishop is on square 2 ('''L=2'''). |
||
4. Then the position number is given by '''4(4(6N + Q)+D)+L''', which reduces to '''96N + 16Q + 4D + L'''. In our example, that's 96×5 + 16×2 + 4×1 + 2 = 480 + 32 + 4 + 2 = 518. |
4. Then the position number is given by '''4(4(6N + Q)+D)+L''', which reduces to '''96N + 16Q + 4D + L'''. In our example, that's 96×5 + 16×2 + 4×1 + 2 = 480 + 32 + 4 + 2 = 518. |
||
Note that an earlier iteration of this page contained an incorrect description of the algorithm which would give the same SP-ID for both of the following two positions. |
|||
RQNBBKRN = 601 |
|||
RNQBBKRN = 617 |
|||
=={{header|11l}}== |
|||
{{trans|Python}} |
|||
<syntaxhighlight lang="11l"> |
|||
F validate_position(String candidate) |
|||
assert(candidate.len == 8, ‘candidate position has invalid len = ’candidate.len) |
|||
V valid_pieces = [‘R’ = 2, ‘N’ = 2, ‘B’ = 2, ‘Q’ = 1, ‘K’ = 1] |
|||
assert(Set(Array(candidate)) == Set(valid_pieces.keys()), ‘candidate position contains invalid pieces’) |
|||
L(piece_type) valid_pieces.keys() |
|||
assert(candidate.count(piece_type) == valid_pieces[piece_type], ‘piece type '’piece_type‘' has invalid count’) |
|||
V bishops_pos = enumerate(Array(candidate)).filter((index, value) -> value == ‘B’).map((index, value) -> index) |
|||
assert(bishops_pos[0] % 2 != bishops_pos[1] % 2, ‘candidate position has both bishops in the same color’) |
|||
assert(candidate.filter(piece -> piece C ‘RK’) == [‘R’, ‘K’, ‘R’], ‘candidate position has K outside of RR’) |
|||
F calc_position(String start_pos) |
|||
validate_position(start_pos) |
|||
V subset_step1 = start_pos.filter(piece -> piece !C ‘QB’) |
|||
V nights_positions = enumerate(subset_step1).filter((index, value) -> value == ‘N’).map((index, value) -> index) |
|||
V nights_table = [(0, 1) = 0, |
|||
(0, 2) = 1, |
|||
(0, 3) = 2, |
|||
(0, 4) = 3, |
|||
(1, 2) = 4, |
|||
(1, 3) = 5, |
|||
(1, 4) = 6, |
|||
(2, 3) = 7, |
|||
(2, 4) = 8, |
|||
(3, 4) = 9] |
|||
V n = nights_table[(nights_positions[0], nights_positions[1])] |
|||
V subset_step2 = start_pos.filter(piece -> piece != ‘B’) |
|||
V q = subset_step2.index(‘Q’) |
|||
V dark_squares = enumerate(Array(start_pos)).filter((index, piece) -> index C Array((0.<9).step(2))).map((index, piece) -> piece) |
|||
V light_squares = enumerate(Array(start_pos)).filter((index, piece) -> index C Array((1.<9).step(2))).map((index, piece) -> piece) |
|||
V d = dark_squares.index(‘B’) |
|||
V l = light_squares.index(‘B’) |
|||
R 4 * (4 * (6*n + q) + d) + l |
|||
L(example) [‘QNRBBNKR’, ‘RNBQKBNR’, ‘RQNBBKRN’, ‘RNQBBKRN’] |
|||
print(‘Position: ’example‘; Chess960 PID= ’calc_position(example)) |
|||
</syntaxhighlight> |
|||
{{out}} |
|||
<pre> |
|||
Position: QNRBBNKR; Chess960 PID= 105 |
|||
Position: RNBQKBNR; Chess960 PID= 518 |
|||
Position: RQNBBKRN; Chess960 PID= 601 |
|||
Position: RNQBBKRN; Chess960 PID= 617 |
|||
</pre> |
|||
=={{header|BASIC}}== |
|||
==={{header|Commodore BASIC}}=== |
|||
{{works with|Commodore BASIC|2.0}} |
|||
Unlike the solution for the reverse task, which uses DO/LOOP and so requires at least Commodore BASIC 3.5, this should work on any version. |
|||
<syntaxhighlight lang="basic">100 REM DERIVE SP-ID FROM CHESS960 POS |
|||
110 READ A$: IF A$="" THEN END |
|||
120 PRINT A$":"; |
|||
130 GOSUB 170 |
|||
140 PRINT SP |
|||
150 GOTO 110 |
|||
160 DATA QNRBBNKR, RNBQKBNR, RQNBBKRN, RNQBBKRN, |
|||
170 IF LEN(A$)=8 THEN 190 |
|||
180 PRINT "ARRAY MUST BE 8 PIECES.": SP=-1: RETURN |
|||
190 K=0:Q=0:B=0:N=0:R=0 |
|||
200 FOR I=0 TO 7 |
|||
210 : K(I)=0:Q(I)=0:B(I)=0:N(I)=0:R(I)=0 |
|||
220 NEXT I |
|||
230 FOR I=1 TO 8 |
|||
240 : P$=MID$(A$,I,1) |
|||
250 : IF P$="Q" THEN Q(Q)=I: Q=Q+1: GOTO 310 |
|||
260 : IF P$="K" THEN K(K)=I: K=K+1: GOTO 310 |
|||
270 : IF P$="B" THEN B(B)=I: B=B+1: GOTO 310 |
|||
280 : IF P$="N" THEN N(N)=I: N=N+1: GOTO 310 |
|||
290 : IF P$="R" THEN R(R)=I: R=R+1: GOTO 310 |
|||
300 : PRINT "ILLEGAL PIECE '"P$"'.": SP=-1: RETURN |
|||
310 NEXT I |
|||
320 IF K<>1 THEN PRINT "THERE MUST BE EXACTLY ONE KING.": SP=-1: RETURN |
|||
330 IF Q<>1 THEN PRINT "THERE MUST BE EXACTLY ONE QUEEN.": SP=-1: RETURN |
|||
340 IF B<>2 THEN PRINT "THERE MUST BE EXACTLY TWO BISHOPS.": SP=-1: RETURN |
|||
350 IF N<>2 THEN PRINT "THERE MUST BE EXACTLY TWO KNIGHTS.": SP=-1: RETURN |
|||
360 IF R<>2 THEN PRINT "THERE MUST BE EXACTLY TWO ROOKS.": SP=-1: RETURN |
|||
370 IF (K(0) > R(0)) AND (K(0) < R(1)) THEN 390 |
|||
380 PRINT "KING MUST BE BETWEEN THE ROOKS.": SP=-1: RETURN |
|||
390 IF (B(0) AND 1) <> (B(1) AND 1) THEN 410 |
|||
400 PRINT "BISHOPS MUST BE ON OPPOSITE COLORS.": SP=-1: RETURN |
|||
410 FOR I=0 TO 1 |
|||
420 : N=N(I) |
|||
430 : IF N(I)>Q(I) THEN N=N-1 |
|||
440 : FOR J=0 TO 1 |
|||
450 : IF N(I)>B(J) THEN N=N-1 |
|||
460 : NEXT J |
|||
470 : N(I)=N |
|||
480 NEXT I |
|||
490 N0=1: N1=2 |
|||
500 FOR N=0 TO 9 |
|||
510 : IF N0=N(0) AND N1=N(1) THEN 550 |
|||
520 : N1=N1+1 |
|||
530 : IF N1>5 THEN N0=N0+1: N1=N0+1 |
|||
540 NEXT N |
|||
550 Q=Q(0)-1 |
|||
560 FOR I=0 TO 1 |
|||
570 : IF Q(0)>B(I) THEN Q=Q-1 |
|||
580 NEXT I |
|||
590 FOR I=0 TO 1 |
|||
600 : B=B(I)-1 |
|||
610 : IF B AND 1 THEN L=INT(B/2) |
|||
620 : IF (B AND 1)=0 THEN D=B/2 |
|||
630 NEXT I |
|||
640 SP = 96*N+16*Q+4*D+L |
|||
650 RETURN</syntaxhighlight> |
|||
{{Out}} |
|||
<pre>READY. |
|||
RUN |
|||
QNRBBNKR: 105 |
|||
RNBQKBNR: 518 |
|||
RQNBBKRN: 601 |
|||
RNQBBKRN: 617 |
|||
READY.</pre> |
|||
==={{header|FreeBASIC}}=== |
|||
<syntaxhighlight lang="freebasic">Sub SP_ID(PosicPiezas As String) |
|||
Dim As String pieza |
|||
Dim As Integer pQ(), pK(), pB(), pN(), pR(), i, j |
|||
Dim As Integer Q, K, B, N, R, L, D |
|||
For i = 1 To 8 |
|||
pieza = Mid(PosicPiezas, i, 1) |
|||
Select Case pieza |
|||
Case "Q" |
|||
Redim Preserve pQ(Q) : pQ(Q) = i: Q += 1 |
|||
Case "K" |
|||
Redim Preserve pK(K) : pK(K) = i: K += 1 |
|||
Case "B" |
|||
Redim Preserve pB(B) : pB(B) = i: B += 1 |
|||
Case "N" |
|||
Redim Preserve pN(N) : pN(N) = i: N += 1 |
|||
Case "R" |
|||
Redim Preserve pR(R) : pR(R) = i: R += 1 |
|||
Case Else |
|||
Print "ILLEGAL PIECE '"; pieza; "'.": Exit Sub |
|||
End Select |
|||
Next i |
|||
If K <> 1 Then Print "THERE MUST BE EXACTLY ONE KING." |
|||
If Q <> 1 Then Print "THERE MUST BE EXACTLY ONE QUEEN." |
|||
If B <> 2 Then Print "THERE MUST BE EXACTLY TWO BISHOPS." |
|||
If N <> 2 Then Print "THERE MUST BE EXACTLY TWO KNIGHTS." |
|||
If R <> 2 Then Print "THERE MUST BE EXACTLY TWO ROOKS." |
|||
If Not (pK(0) > pR(0)) And (pK(0) < pR(1)) Then Print "KING MUST BE BETWEEN THE ROOKS." |
|||
If Not (pB(0) And 1) <> (pB(1) And 1) Then Print "BISHOPS MUST BE ON OPPOSITE COLORS." |
|||
For i = 0 To 1 |
|||
N = pN(i) |
|||
If pN(i) > pQ(i) Then N -= 1 |
|||
For j = 0 To 1 |
|||
If pN(i) > pB(j) Then N -= 1 |
|||
Next j |
|||
pN(i) = N |
|||
Next i |
|||
Dim As Integer N0 = 1, N1 = 2 |
|||
For N = 0 To 9 |
|||
If N0 = pN(0) And N1 = pN(1) Then Exit For |
|||
N1 += 1 |
|||
If N1 > 5 Then N0 += 1: N1 = N0 + 1 |
|||
Next N |
|||
Q = pQ(0) - 1 |
|||
For i = 0 To 1 |
|||
If pQ(0) > pB(i) Then Q -= 1 |
|||
Next i |
|||
For i = 0 To 1 |
|||
B = pB(i) - 1 |
|||
If B And 1 Then L = Int(B / 2) |
|||
If (B And 1) = 0 Then D = B / 2 |
|||
Next i |
|||
Print PosicPiezas; " has SP_ID of"; 96 * N + 16 * Q + 4 * D + L |
|||
End Sub |
|||
SP_ID("QNRBBNKR") |
|||
Print |
|||
SP_ID("RNBQKBNR") |
|||
Print |
|||
SP_ID("RQNBBKRN") |
|||
Print |
|||
SP_ID("RNQBBKRN") |
|||
Sleep</syntaxhighlight> |
|||
{{out}} |
|||
<pre>QNRBBNKR has SP_ID of 105 |
|||
RNBQKBNR has SP_ID of 518 |
|||
RQNBBKRN has SP_ID of 601 |
|||
RNQBBKRN has SP_ID of 617 |
|||
</pre> |
|||
==={{header|QBasic}}=== |
|||
{{works with|QBasic|1.1}} |
|||
{{trans|Commodore BASIC}} |
|||
<syntaxhighlight lang="qbasic">Cls |
|||
Print "Enter start array as seen by white." |
|||
120 Print |
|||
Print "Starting array"; |
|||
Input Ar$ |
|||
Print |
|||
If Len(Ar$) = 0 Then End |
|||
If Len(Ar$) = 8 Then 170 |
|||
Print "Array must be 8 pieces.": GoTo 120 |
|||
170 For I = 1 To 8 |
|||
P$ = Mid$(Ar$, I, 1) |
|||
If P$ = "Q" Or P$ = "q" Then Q(Q) = I: Q = Q + 1: GoTo 250 |
|||
If P$ = "K" Or P$ = "k" Then K(K) = I: K = K + 1: GoTo 250 |
|||
If P$ = "B" Or P$ = "b" Then B(B) = I: B = B + 1: GoTo 250 |
|||
If P$ = "N" Or P$ = "n" Then N(N) = I: N = N + 1: GoTo 250 |
|||
If P$ = "R" Or P$ = "r" Then R(R) = I: R = R + 1: GoTo 250 |
|||
Print "Illegal piece '"; P$; "'.": GoTo 120 |
|||
250 Next I |
|||
If K <> 1 Then Print "There must be exactly one King.": GoTo 120 |
|||
If Q <> 1 Then Print "There must be exactly one Queen.": GoTo 120 |
|||
If B <> 2 Then Print "There must be exactly two Bishops.": GoTo 120 |
|||
If N <> 2 Then Print "There must be exactly two Knights.": GoTo 120 |
|||
If R <> 2 Then Print "There must be exactly two Rooks.": GoTo 120 |
|||
If (K(0) > R(0)) And (K(0) < R(1)) Then 330 |
|||
Print "King must be between the Rooks.": GoTo 120 |
|||
330 If (B(0) And 1) <> (B(1) And 1) Then 350 |
|||
Print "Bishops must be on opposite colors.": GoTo 120 |
|||
350 For I = 0 To 1 |
|||
N = N(I) |
|||
If N(I) > Q(I) Then N = N - 1 |
|||
For J = 0 To 1 |
|||
If N(I) > B(J) Then N = N - 1 |
|||
Next J |
|||
N(I) = N |
|||
Next I |
|||
N0 = 1: N1 = 2 |
|||
For N = 0 To 9 |
|||
If N0 = N(0) And N1 = N(1) Then 490 |
|||
N1 = N1 + 1 |
|||
If N1 > 5 Then N0 = N0 + 1: N1 = N0 + 1 |
|||
Next N |
|||
490 Q = Q(0) - 1 |
|||
For I = 0 To 1 |
|||
If Q(0) > B(I) Then Q = Q - 1 |
|||
Next I |
|||
For I = 0 To 1 |
|||
B = B(I) - 1 |
|||
If B And 1 Then L = Int(B / 2) |
|||
If (B And 1) = 0 Then D = B / 2 |
|||
Next I |
|||
Print "SP-ID ="; 96 * N + 16 * Q + 4 * D + L |
|||
End |
|||
</syntaxhighlight> |
|||
{{out}} |
|||
<pre>Enter start array as seen by White. |
|||
Starting array? qnrbbnkr |
|||
SP-ID = 105 |
|||
Starting array? RNBQKBNR |
|||
SP-ID = 518 |
|||
Starting array? RQNBBKRN |
|||
SP-ID = 601 |
|||
Starting array? RNQBBKRN |
|||
SP-ID = 617 |
|||
</pre> |
|||
=={{header|C++}}== |
|||
<syntaxhighlight lang="c++"> |
|||
#include <algorithm> |
|||
#include <cstdint> |
|||
#include <stdexcept> |
|||
#include <iostream> |
|||
#include <string> |
|||
#include <map> |
|||
#include <set> |
|||
#include <vector> |
|||
const std::set<std::pair<char, int32_t>> correct_pieces = { std::make_pair('R', 2), std::make_pair('N', 2), |
|||
std::make_pair('B', 2), std::make_pair('Q', 1), std::make_pair('K', 1) }; |
|||
std::map<std::vector<int32_t>, int32_t> knights_table = { |
|||
{ std::vector<int32_t>{ 0, 1 }, 0 }, |
|||
{ std::vector<int32_t>{ 0, 2 }, 1 }, |
|||
{ std::vector<int32_t>{ 0, 3 }, 2 }, |
|||
{ std::vector<int32_t>{ 0, 4 }, 3 }, |
|||
{ std::vector<int32_t>{ 1, 2 }, 4 }, |
|||
{ std::vector<int32_t>{ 1, 3 }, 5 }, |
|||
{ std::vector<int32_t>{ 1, 4 }, 6 }, |
|||
{ std::vector<int32_t>{ 2, 3 }, 7 }, |
|||
{ std::vector<int32_t>{ 2, 4 }, 8 }, |
|||
{ std::vector<int32_t>{ 3, 4 }, 9 } }; |
|||
void validate(const std::string& position) { |
|||
if ( position.length() != 8 ) { |
|||
throw std::invalid_argument("Chess position has invalid length" + std::to_string(position.length())); |
|||
} |
|||
std::map<char, int32_t> position_map; |
|||
for ( const char& ch : position ) { |
|||
if ( position_map.find(ch) == position_map.end() ) { |
|||
position_map.emplace(ch, 1); |
|||
} else { |
|||
position_map[ch]++; |
|||
} |
|||
} |
|||
std::set<std::pair<char, int32_t>> pieces; |
|||
std::transform(position_map.begin(), position_map.end(), std::inserter(pieces, pieces.begin()), |
|||
[](const std::pair<char, int32_t>& entry) { return entry; }); |
|||
if ( pieces != correct_pieces ) { |
|||
throw std::invalid_argument("Chess position contains incorrect pieces."); |
|||
} |
|||
const std::vector<uint64_t> bishops = { position.find_first_of('B'), position.find_last_of('B') }; |
|||
if ( ( bishops[1] - bishops[0] ) % 2 == 0 ) { |
|||
throw std::invalid_argument("Bishops must be on different coloured squares."); |
|||
} |
|||
std::vector<uint64_t> rook_king = |
|||
{ position.find_first_of('R'), position.find_first_of('K'), position.find_last_of('R') }; |
|||
if ( ! ( rook_king[0] < rook_king[1] && rook_king[1] < rook_king[2] ) ) { |
|||
throw std::invalid_argument("The king must be between the two rooks."); |
|||
} |
|||
} |
|||
int32_t calculate_SPID(std::string& position) { |
|||
const int32_t index_one = position.find_first_of('B'); |
|||
const int32_t index_two = position.find_last_of('B'); |
|||
const int32_t D = ( index_one % 2 == 0 ) ? index_one / 2 : index_two / 2; |
|||
const int32_t L = ( index_one % 2 == 0 ) ? index_two / 2 : index_one / 2; |
|||
position.erase(remove_if(position.begin(), position.end(), |
|||
[](const char& ch){ return ch == 'B'; }), position.end()); |
|||
const uint64_t Q = position.find_first_of('Q'); |
|||
position.erase(remove_if(position.begin(), position.end(), |
|||
[](const char& ch){ return ch == 'Q'; }), position.end()); |
|||
const int32_t N = |
|||
knights_table[ { (int32_t) position.find_first_of('N'), (int32_t) position.find_last_of('N') } ]; |
|||
return 96 * N + 16 * Q + 4 * D + L; |
|||
} |
|||
int main() { |
|||
std::vector<std::string> positions = { "QNRBBNKR", "RNBQKBNR", "RQNBBKRN", "RNQBBKRN" }; |
|||
for ( std::string& position : positions ) { |
|||
validate(position); |
|||
std::cout << "Position " << position << " has Chess960 SP-ID = " << calculate_SPID(position) << std::endl; |
|||
} |
|||
} |
|||
</syntaxhighlight> |
|||
{{ out }} |
|||
<pre> |
|||
Position QNRBBNKR has Chess960 SP-ID = 105 |
|||
Position RNBQKBNR has Chess960 SP-ID = 518 |
|||
Position RQNBBKRN has Chess960 SP-ID = 601 |
|||
Position RNQBBKRN has Chess960 SP-ID = 617 |
|||
</pre> |
|||
=={{header|Common Lisp}}== |
|||
<syntaxhighlight lang="lisp">; make sure string is a valid Chess960 starting array |
|||
(defun valid-array-p (start-array) |
|||
(and (string-equal (sort (copy-seq start-array) #'string-lessp) "BBKNNQRR") ; right pieces |
|||
(not (equal (mod (position #\B start-array) 2) ; bishops on opposite colors |
|||
(mod (position #\B start-array :from-end t) 2))) |
|||
(< (position #\R start-array) (position #\K start-array)) ; king between two rooks |
|||
(< (position #\K start-array) (position #\R start-array :from-end t)))) |
|||
; find Start Position IDentifier for a Chess960 setup |
|||
(defun sp-id (start-array) |
|||
(if (not (valid-array-p start-array)) |
|||
-1 |
|||
(let* ((bishopless (remove #\B start-array)) |
|||
(queenless (remove #\Q bishopless)) |
|||
(n5n-pattern (substitute-if-not #\- (lambda (ch) (eql ch #\N)) queenless)) |
|||
(n5n-table '("NN---" "N-N--" "N--N-" "N---N" "-NN--" "-N-N-" "-N--N" "--NN-" "--N-N" "---NN")) |
|||
(knights (position n5n-pattern n5n-table :test #'string-equal)) |
|||
(queen (position #\Q bishopless)) |
|||
(left-bishop (position #\B start-array)) |
|||
(right-bishop (position #\B start-array :from-end t))) |
|||
; map each bishop to its color complex and position within those four squares |
|||
(destructuring-bind (dark-bishop light-bishop) |
|||
(mapcar (lambda (p) (floor p 2)) |
|||
(cond ((zerop (mod left-bishop 2)) (list left-bishop right-bishop)) |
|||
(t (list right-bishop left-bishop)))) |
|||
(+ (* 96 knights) (* 16 queen) (* 4 dark-bishop) light-bishop))))) |
|||
(loop for ary in '("RNBQKBNR" "QNRBBNKR" "RQNBBKRN" "RNQBBKRN") doing |
|||
(format t "~a: ~a~%" ary (sp-id ary))) |
|||
</syntaxhighlight> |
|||
{{Out}} |
|||
<pre>RNBQKBNR: 518 |
|||
QNRBBNKR: 105 |
|||
RQNBBKRN: 601 |
|||
RNQBBKRN: 617</pre> |
|||
=={{header|Factor}}== |
=={{header|Factor}}== |
||
{{works with|Factor|0.99 2021-06-02}} |
{{works with|Factor|0.99 2021-06-02}} |
||
< |
<syntaxhighlight lang="factor">USING: assocs assocs.extras combinators formatting kernel |
||
literals math math.combinatorics sequences sequences.extras sets |
literals math math.combinatorics sequences sequences.extras sets |
||
strings ; |
strings ; |
||
IN: scratchpad |
|||
! ====== optional error-checking ====== |
! ====== optional error-checking ====== |
||
Line 68: | Line 500: | ||
: n ( str -- n ) "QB" without knightify table index ; |
: n ( str -- n ) "QB" without knightify table index ; |
||
: q ( str -- q ) " |
: q ( str -- q ) "B" without CHAR: Q swap index ; |
||
: d ( str -- d ) CHAR: B swap <evens> index ; |
: d ( str -- d ) CHAR: B swap <evens> index ; |
||
Line 83: | Line 515: | ||
"QNRBBNKR" sp-id. |
"QNRBBNKR" sp-id. |
||
"RNBQKBNR" sp-id. |
"RNBQKBNR" sp-id. |
||
"RQNBBKRN" sp-id. |
|||
"RNQBBKRN" sp-id.</syntaxhighlight> |
|||
{{out}} |
{{out}} |
||
<pre> |
<pre> |
||
QNRBBNKR / ♕♘♖♗♗♘♔♖: 105 |
QNRBBNKR / ♕♘♖♗♗♘♔♖: 105 |
||
RNBQKBNR / ♖♘♗♕♔♗♘♖: 518 |
RNBQKBNR / ♖♘♗♕♔♗♘♖: 518 |
||
RQNBBKRN / ♖♕♘♗♗♔♖♘: 601 |
|||
RNQBBKRN / ♖♘♕♗♗♔♖♘: 617 |
|||
</pre> |
|||
=={{header|Go}}== |
|||
{{trans|Wren}} |
|||
<syntaxhighlight lang="go">package main |
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"strings" |
|||
) |
|||
var glyphs = []rune("♜♞♝♛♚♖♘♗♕♔") |
|||
var names = map[rune]string{'R': "rook", 'N': "knight", 'B': "bishop", 'Q': "queen", 'K': "king"} |
|||
var g2lMap = map[rune]string{ |
|||
'♜': "R", '♞': "N", '♝': "B", '♛': "Q", '♚': "K", |
|||
'♖': "R", '♘': "N", '♗': "B", '♕': "Q", '♔': "K", |
|||
} |
|||
var ntable = map[string]int{"01": 0, "02": 1, "03": 2, "04": 3, "12": 4, "13": 5, "14": 6, "23": 7, "24": 8, "34": 9} |
|||
func g2l(pieces string) string { |
|||
lets := "" |
|||
for _, p := range pieces { |
|||
lets += g2lMap[p] |
|||
} |
|||
return lets |
|||
} |
|||
func spid(pieces string) int { |
|||
pieces = g2l(pieces) // convert glyphs to letters |
|||
/* check for errors */ |
|||
if len(pieces) != 8 { |
|||
log.Fatal("There must be exactly 8 pieces.") |
|||
} |
|||
for _, one := range "KQ" { |
|||
count := 0 |
|||
for _, p := range pieces { |
|||
if p == one { |
|||
count++ |
|||
} |
|||
} |
|||
if count != 1 { |
|||
log.Fatalf("There must be one %s.", names[one]) |
|||
} |
|||
} |
|||
for _, two := range "RNB" { |
|||
count := 0 |
|||
for _, p := range pieces { |
|||
if p == two { |
|||
count++ |
|||
} |
|||
} |
|||
if count != 2 { |
|||
log.Fatalf("There must be two %s.", names[two]) |
|||
} |
|||
} |
|||
r1 := strings.Index(pieces, "R") |
|||
r2 := strings.Index(pieces[r1+1:], "R") + r1 + 1 |
|||
k := strings.Index(pieces, "K") |
|||
if k < r1 || k > r2 { |
|||
log.Fatal("The king must be between the rooks.") |
|||
} |
|||
b1 := strings.Index(pieces, "B") |
|||
b2 := strings.Index(pieces[b1+1:], "B") + b1 + 1 |
|||
if (b2-b1)%2 == 0 { |
|||
log.Fatal("The bishops must be on opposite color squares.") |
|||
} |
|||
/* compute SP_ID */ |
|||
piecesN := strings.ReplaceAll(pieces, "Q", "") |
|||
piecesN = strings.ReplaceAll(piecesN, "B", "") |
|||
n1 := strings.Index(piecesN, "N") |
|||
n2 := strings.Index(piecesN[n1+1:], "N") + n1 + 1 |
|||
np := fmt.Sprintf("%d%d", n1, n2) |
|||
N := ntable[np] |
|||
piecesQ := strings.ReplaceAll(pieces, "B", "") |
|||
Q := strings.Index(piecesQ, "Q") |
|||
D := strings.Index("0246", fmt.Sprintf("%d", b1)) |
|||
L := strings.Index("1357", fmt.Sprintf("%d", b2)) |
|||
if D == -1 { |
|||
D = strings.Index("0246", fmt.Sprintf("%d", b2)) |
|||
L = strings.Index("1357", fmt.Sprintf("%d", b1)) |
|||
} |
|||
return 96*N + 16*Q + 4*D + L |
|||
} |
|||
func main() { |
|||
for _, pieces := range []string{"♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♖♕♘♗♗♔♖♘", "♖♘♕♗♗♔♖♘"} { |
|||
fmt.Printf("%s or %s has SP-ID of %d\n", pieces, g2l(pieces), spid(pieces)) |
|||
} |
|||
}</syntaxhighlight> |
|||
{{out}} |
|||
<pre> |
|||
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 |
|||
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 |
|||
♖♕♘♗♗♔♖♘ or RQNBBKRN has SP-ID of 601 |
|||
♖♘♕♗♗♔♖♘ or RNQBBKRN has SP-ID of 617 |
|||
</pre> |
|||
=={{header|J}}== |
|||
Implementation:<syntaxhighlight lang="j">REF=: {{ |
|||
'N Q B0 B1'=. 0 6 4 4 #: y |
|||
s=. 'B' (0 1+2*B0,B1)} 8#' ' |
|||
s=. 'Q' (Q{I.' '=s)} s |
|||
s=. 'N' ((N{(#~ 2=+/"1)#:i.-32){&I.' '=s)} s |
|||
'RKR' (I.' '=s)} s |
|||
}}"0 i.960 |
|||
c960=: {{ r=. REF i. rplc&((u:9812+i.12);&>12$'KQRBNP') 7 u:deb y assert. r<#REF }}</syntaxhighlight> |
|||
Examples: |
|||
<syntaxhighlight lang="j"> c960'♕♘♖♗♗♘♔♖' |
|||
105 |
|||
c960'♛♞♜♝♝♞♚♜' |
|||
105 |
|||
c960'RNBQKBNR' |
|||
518 |
|||
c960'RQNBBKRN' |
|||
601 |
|||
c960'RNQBBKRN' |
|||
617</syntaxhighlight> |
|||
=={{header|Java}}== |
|||
<syntaxhighlight lang="java"> |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.function.Predicate; |
|||
public final class Chess960SPID { |
|||
public static void main(String[] aArgs) { |
|||
String[] positions = { "QNRBBNKR", "RNBQKBNR", "RQNBBKRN", "RNQBBKRN" }; |
|||
createKnightsTable(); |
|||
createCorrectPieces(); |
|||
for ( String position : positions ) { |
|||
validate(position); |
|||
System.out.println("Position " + position + " has Chess960 SP-ID = " + calculateSPID(position)); |
|||
} |
|||
} |
|||
private static void validate(String aPosition) { |
|||
if ( aPosition.length() != 8 ) { |
|||
throw new AssertionError("Chess position has invalid length: " + aPosition.length() + "."); |
|||
} |
|||
Map<Character, Integer> pieces = new HashMap<Character, Integer>(); |
|||
for ( char ch : aPosition.toCharArray() ) { |
|||
pieces.merge(ch, 1, (oldV, newV) -> oldV + 1); |
|||
} |
|||
if ( ! pieces.entrySet().equals(correctPieces) ) { |
|||
throw new AssertionError("Chess position contains incorrect pieces."); |
|||
} |
|||
List<Integer> bishops = List.of(aPosition.indexOf('B'), aPosition.lastIndexOf('B')); |
|||
if ( ( bishops.get(1) - bishops.get(0) ) % 2 == 0 ) { |
|||
throw new AssertionError("Bishops must be on different coloured squares."); |
|||
} |
|||
List<Integer> rookKing = List.of(aPosition.indexOf('R'), aPosition.indexOf('K'), aPosition.lastIndexOf('R')); |
|||
if ( ! ( rookKing.get(0) < rookKing.get(1) && rookKing.get(1) < rookKing.get(2) ) ) { |
|||
throw new AssertionError("The king must be between the two rooks."); |
|||
} |
|||
} |
|||
private static int calculateSPID(String aPosition) { |
|||
String noBishopsOrQueen = retainIf(aPosition, s -> s != 'B' && s != 'Q'); |
|||
final int N = knightsTable.get(List.of(noBishopsOrQueen.indexOf('N'), noBishopsOrQueen.lastIndexOf('N'))); |
|||
String noBishops = retainIf(aPosition, s -> s != 'B'); |
|||
final int Q = noBishops.indexOf('Q'); |
|||
final int indexOne = aPosition.indexOf('B'); |
|||
final int indexTwo = aPosition.lastIndexOf('B'); |
|||
final int D = ( indexOne % 2 == 0 ) ? indexOne / 2 : indexTwo / 2; |
|||
final int L = ( indexOne % 2 == 0 ) ? indexTwo / 2 : indexOne / 2; |
|||
return 96 * N + 16 * Q + 4 * D + L; |
|||
} |
|||
private static String retainIf(String aText, Predicate<Character> aPredicate) { |
|||
return aText.chars() |
|||
.mapToObj( i -> (char) i ) |
|||
.filter(aPredicate) |
|||
.map(String::valueOf) |
|||
.reduce("", String::concat); |
|||
} |
|||
private static void createKnightsTable() { |
|||
knightsTable = new HashMap<List<Integer>, Integer>(); |
|||
knightsTable.put(List.of(0, 1), 0); |
|||
knightsTable.put(List.of(0, 2), 1); |
|||
knightsTable.put(List.of(0, 3), 2); |
|||
knightsTable.put(List.of(0, 4), 3); |
|||
knightsTable.put(List.of(1, 2), 4); |
|||
knightsTable.put(List.of(1, 3), 5); |
|||
knightsTable.put(List.of(1, 4), 6); |
|||
knightsTable.put(List.of(2, 3), 7); |
|||
knightsTable.put(List.of(2, 4), 8); |
|||
knightsTable.put(List.of(3, 4), 9); |
|||
} |
|||
private static void createCorrectPieces() { |
|||
correctPieces = Set.of( |
|||
Map.entry('R', 2), Map.entry('N', 2), Map.entry('B', 2), Map.entry('Q', 1), Map.entry('K', 1) ); |
|||
} |
|||
private static Map<List<Integer>, Integer> knightsTable; |
|||
private static Set<Map.Entry<Character, Integer>> correctPieces; |
|||
} |
|||
</syntaxhighlight> |
|||
{{ out }} |
|||
<pre> |
|||
Position QNRBBNKR has Chess960 SP-ID = 105 |
|||
Position RNBQKBNR has Chess960 SP-ID = 518 |
|||
Position RQNBBKRN has Chess960 SP-ID = 601 |
|||
Position RNQBBKRN has Chess960 SP-ID = 617 |
|||
</pre> |
</pre> |
||
=={{header|Julia}}== |
=={{header|Julia}}== |
||
< |
<syntaxhighlight lang="julia">const whitepieces = "♖♘♗♕♔♗♘♖♙" |
||
const whitechars = "rnbqkp" |
const whitechars = "rnbqkp" |
||
const blackpieces = "♜♞♝♛♚♝♞♜♟" |
const blackpieces = "♜♞♝♛♚♝♞♜♟" |
||
Line 118: | Line 784: | ||
knightpos1, knightpos2 = findfirst(c -> c =='N', noQB), findlast(c -> c =='N', noQB) |
knightpos1, knightpos2 = findfirst(c -> c =='N', noQB), findlast(c -> c =='N', noQB) |
||
N = findfirst(s -> s == 10 * knightpos1 + knightpos2, knighttable) - 1 |
N = findfirst(s -> s == 10 * knightpos1 + knightpos2, knighttable) - 1 |
||
Q = findfirst(c -> c == 'Q', replace(a, " |
Q = findfirst(c -> c == 'Q', replace(a, "B" => "")) - 1 |
||
bishoppositions = [findfirst(c -> c =='B', a), findlast(c -> c =='B', a)] |
bishoppositions = [findfirst(c -> c =='B', a), findlast(c -> c =='B', a)] |
||
if isodd(bishoppositions[2]) |
if isodd(bishoppositions[2]) |
||
Line 128: | Line 794: | ||
end |
end |
||
for position in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖"] |
for position in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♖♕♘♗♗♔♖♘", "♖♘♕♗♗♔♖♘"] |
||
println(collect(position), " => ", chess960spid(position)) |
println(collect(position), " => ", chess960spid(position)) |
||
end |
end |
||
</ |
</syntaxhighlight>{{out}} |
||
<pre> |
<pre> |
||
['♕', '♘', '♖', '♗', '♗', '♘', '♔', '♖'] => 105 |
['♕', '♘', '♖', '♗', '♗', '♘', '♔', '♖'] => 105 |
||
['♖', '♘', '♗', '♕', '♔', '♗', '♘', '♖'] => 518 |
['♖', '♘', '♗', '♕', '♔', '♗', '♘', '♖'] => 518 |
||
['♖', '♕', '♘', '♗', '♗', '♔', '♖', '♘'] => 601 |
|||
['♖', '♘', '♕', '♗', '♗', '♔', '♖', '♘'] => 617 |
|||
</pre> |
</pre> |
||
=={{header|Nim}}== |
=={{header|Nim}}== |
||
{{trans|Wren}} |
{{trans|Wren}} |
||
< |
<syntaxhighlight lang="nim">import sequtils, strformat, strutils, sugar, tables, unicode |
||
type Piece {.pure.} = enum Rook = "R", Knight = "N", Bishop = "B", Queen = "Q", King = "K" |
type Piece {.pure.} = enum Rook = "R", Knight = "N", Bishop = "B", Queen = "Q", King = "K" |
||
Line 187: | Line 855: | ||
let n = NTable[piecesN.positions(Knight)] |
let n = NTable[piecesN.positions(Knight)] |
||
let piecesQ = pieces.filterIt(it != |
let piecesQ = pieces.filterIt(it != Bishop) |
||
let q = piecesQ.find(Queen) |
let q = piecesQ.find(Queen) |
||
Line 197: | Line 865: | ||
for glyphs in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖"]: |
for glyphs in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♖♕♘♗♗♔♖♘", "♖♘♕♗♗♔♖♘"]: |
||
echo &"{glyphs} or {glyphs.toPieces().join()} has SP-ID of {glyphs.spid()}"</ |
echo &"{glyphs} or {glyphs.toPieces().join()} has SP-ID of {glyphs.spid()}"</syntaxhighlight> |
||
{{out}} |
{{out}} |
||
<pre>♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 |
<pre>♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 |
||
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 |
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 |
||
♖♕♘♗♗♔♖♘ or RQNBBKRN has SP-ID of 601 |
|||
♖♘♕♗♗♔♖♘ or RNQBBKRN has SP-ID of 617</pre> |
|||
=={{header|Perl}}== |
=={{header|Perl}}== |
||
{{trans|Raku}} |
{{trans|Raku}} |
||
< |
<syntaxhighlight lang="perl">use v5.36; |
||
use warnings; |
|||
use feature 'say'; |
|||
use List::AllUtils 'indexes'; |
use List::AllUtils 'indexes'; |
||
sub sp_id { |
sub sp_id ($setup) { |
||
8 == length $setup or return 'Illegal position: should have exactly eight pieces'; |
|||
my $setup = shift // 'RNBQKBNR'; |
|||
1 == @{[ $setup =~ /$_/g ]} or return "Illegal position: should have exactly one $_" for <K Q>; |
|||
2 == @{[ $setup =~ /$_/g ]} or return "Illegal position: should have exactly two $_\'s" for <B N R>; |
|||
$setup =~ m/R .* K .* R/x or return 'Illegal position: King not between rooks.'; |
|||
2 |
index($setup,'B')%2 != rindex($setup,'B')%2 or return 'Illegal position: Bishops not on opposite colors.'; |
||
$setup =~ m/R .* K .* R/x or die 'Illegal position: King not between rooks.'; |
|||
index($setup,'B')%2 != rindex($setup,'B')%2 or die 'Illegal position: Bishops not on opposite colors.'; |
|||
my @knights = indexes { 'N' eq $_ } split '', $setup =~ s/[QB]//gr; |
my @knights = indexes { 'N' eq $_ } split '', $setup =~ s/[QB]//gr; |
||
my $knight = indexes { join('', @knights) eq $_ } <01 02 03 04 12 13 14 23 24 34>; # combinations(5,2) |
my $knight = indexes { join('', @knights) eq $_ } <01 02 03 04 12 13 14 23 24 34>; # combinations(5,2) |
||
my @bishops = indexes { 'B' eq $_ } split '', $setup; |
my @bishops = indexes { 'B' eq $_ } split '', $setup; |
||
my $dark = int ((grep { $_ % 2 == 0 } @bishops)[0]) / 2; |
my $dark = int ((grep { $_ % 2 == 0 } @bishops)[0]) / 2; |
||
my $light = int ((grep { $_ % 2 == 1 } @bishops)[0]) / 2; |
my $light = int ((grep { $_ % 2 == 1 } @bishops)[0]) / 2; |
||
my $queen = index(($setup =~ s/B//gr), 'Q'); |
my $queen = index(($setup =~ s/B//gr), 'Q'); |
||
int 4*(4*(6*$knight + $queen)+$dark)+$light; |
int 4*(4*(6*$knight + $queen)+$dark)+$light; |
||
} |
} |
||
say "$_ " . sp_id($_) for <QNRBBNKR RNBQKBNR>;</ |
say "$_ " . sp_id($_) for <QNRBBNKR RNBQKBNR RQNBBKRN RNQBBKRN QNBRBNKR>;</syntaxhighlight> |
||
{{out}} |
{{out}} |
||
<pre>QNRBBNKR 105 |
<pre>QNRBBNKR 105 |
||
RNBQKBNR 518 |
RNBQKBNR 518 |
||
RQNBBKRN 601 |
|||
RNQBBKRN 617 |
|||
QNBRBNKR Illegal position: Bishops not on opposite colors.</pre> |
|||
=={{header|Phix}}== |
=={{header|Phix}}== |
||
<!--< |
<!--<syntaxhighlight lang="phix">(phixonline)--> |
||
<span style="color: #008080;">with</span> <span style="color: #008080;">javascript_semantics</span> |
<span style="color: #008080;">with</span> <span style="color: #008080;">javascript_semantics</span> |
||
<span style="color: #008080;">function</span> <span style="color: #000000;">spid</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">s</span><span style="color: #0000FF;">)</span> |
<span style="color: #008080;">function</span> <span style="color: #000000;">spid</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">s</span><span style="color: #0000FF;">)</span> |
||
Line 245: | Line 914: | ||
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">n1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">n2</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">find_all</span><span style="color: #0000FF;">(</span><span style="color: #008000;">'N'</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"out"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"QB"</span><span style="color: #0000FF;">)),</span> |
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">n1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">n2</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">find_all</span><span style="color: #0000FF;">(</span><span style="color: #008000;">'N'</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"out"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"QB"</span><span style="color: #0000FF;">)),</span> |
||
<span style="color: #000000;">N</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{-</span><span style="color: #000000;">2</span><span style="color: #0000FF;">,</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">3</span><span style="color: #0000FF;">,</span><span style="color: #000000;">4</span><span style="color: #0000FF;">}[</span><span style="color: #000000;">n1</span><span style="color: #0000FF;">]+</span><span style="color: #000000;">n2</span><span style="color: #0000FF;">,</span> |
<span style="color: #000000;">N</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{-</span><span style="color: #000000;">2</span><span style="color: #0000FF;">,</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">3</span><span style="color: #0000FF;">,</span><span style="color: #000000;">4</span><span style="color: #0000FF;">}[</span><span style="color: #000000;">n1</span><span style="color: #0000FF;">]+</span><span style="color: #000000;">n2</span><span style="color: #0000FF;">,</span> |
||
<span style="color: #000000;">Q</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">find</span><span style="color: #0000FF;">(</span><span style="color: #008000;">'Q'</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"!="</span><span style="color: #0000FF;">,</span><span style="color: #008000;">' |
<span style="color: #000000;">Q</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">find</span><span style="color: #0000FF;">(</span><span style="color: #008000;">'Q'</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"!="</span><span style="color: #0000FF;">,</span><span style="color: #008000;">'B'</span><span style="color: #0000FF;">))-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> |
||
<span style="color: #000000;">D</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">b</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">odd</span><span style="color: #0000FF;">)[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- (nb not /2)</span> |
<span style="color: #000000;">D</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">b</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">odd</span><span style="color: #0000FF;">)[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- (nb not /2)</span> |
||
<span style="color: #000000;">L</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">b</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">even</span><span style="color: #0000FF;">)[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]/</span><span style="color: #000000;">2</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> |
<span style="color: #000000;">L</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">filter</span><span style="color: #0000FF;">(</span><span style="color: #000000;">b</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">even</span><span style="color: #0000FF;">)[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]/</span><span style="color: #000000;">2</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> |
||
Line 257: | Line 926: | ||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"QNRBBNKR"</span><span style="color: #0000FF;">)</span> |
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"QNRBBNKR"</span><span style="color: #0000FF;">)</span> |
||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"RNBQKBNR"</span><span style="color: #0000FF;">)</span> |
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"RNBQKBNR"</span><span style="color: #0000FF;">)</span> |
||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"RQNBBKRN"</span><span style="color: #0000FF;">)</span> |
|||
<!--</lang>--> |
|||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"RNQBBKRN"</span><span style="color: #0000FF;">)</span> |
|||
<!--</syntaxhighlight>--> |
|||
{{out}} |
{{out}} |
||
<pre> |
<pre> |
||
QNRBBNKR : 105 |
QNRBBNKR : 105 |
||
RNBQKBNR : 518 |
RNBQKBNR : 518 |
||
RQNBBKRN : 601 |
|||
RNQBBKRN : 617</pre> |
|||
To support all those crazy unicode characters just change the start of spid() to: |
|||
<!--<syntaxhighlight lang="phix">(phixonline)--> |
|||
<span style="color: #008080;">function</span> <span style="color: #000000;">spid</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">u</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">sequence</span> <span style="color: #000000;">u32</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">utf8_to_utf32</span><span style="color: #0000FF;">(</span><span style="color: #000000;">u</span><span style="color: #0000FF;">),</span> |
|||
<span style="color: #000000;">c32</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">utf8_to_utf32</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"♜♞♝♛♚♖♘♗♕♔"</span><span style="color: #0000FF;">),</span> |
|||
<span style="color: #000000;">s32</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">substitute_all</span><span style="color: #0000FF;">(</span><span style="color: #000000;">u32</span><span style="color: #0000FF;">,</span><span style="color: #000000;">c32</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"RNBQKRNBQK"</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">string</span> <span style="color: #000000;">s</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">utf32_to_utf8</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s32</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000080;font-style:italic;">--... and add:</span> |
|||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"♕♘♖♗♗♘♔♖"</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"♖♘♗♕♔♗♘♖"</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"♜♛♞♝♝♚♜♞"</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">test</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"♜♞♛♝♝♚♜♞"</span><span style="color: #0000FF;">)</span> |
|||
<!--</syntaxhighlight>--> |
|||
{{out}} |
|||
Note that output on a windows terminal is as expected far from pretty, this is from pwa/p2js |
|||
<pre> |
|||
QNRBBNKR : 105 |
|||
RNBQKBNR : 518 |
|||
RQNBBKRN : 601 |
|||
RNQBBKRN : 617 |
|||
♕♘♖♗♗♘♔♖ : 105 |
|||
♖♘♗♕♔♗♘♖ : 518 |
|||
♜♛♞♝♝♚♜♞ : 601 |
|||
♜♞♛♝♝♚♜♞ : 617 |
|||
</pre> |
</pre> |
||
=={{header| |
=={{header|Python}}== |
||
{{works with|Python|3.10.5 2022-06-28}} |
|||
<lang perl6>#!/usr/bin/env raku |
|||
# derive a chess960 position's SP-ID |
|||
unit sub MAIN($array = "♖♘♗♕♔♗♘♖"); |
|||
<syntaxhighlight lang="python"># optional, but task function depends on it as written |
|||
# standardize on letters for easier processing |
|||
def validate_position(candidate: str): |
|||
my $ascii = $array.trans("♜♞♝♛♚♖♘♗♕♔" => "RNBQKRNBQK"); |
|||
assert ( |
|||
len(candidate) == 8 |
|||
), f"candidate position has invalide len = {len(candidate)}" |
|||
valid_pieces = {"R": 2, "N": 2, "B": 2, "Q": 1, "K": 1} |
|||
# (optional error-checking) |
|||
assert { |
|||
if $ascii.chars != 8 { |
|||
piece for piece in candidate |
|||
die "Illegal position: should have exactly eight pieces\n"; |
|||
} == valid_pieces.keys(), f"candidate position contains invalid pieces" |
|||
} |
|||
for piece_type in valid_pieces.keys(): |
|||
assert ( |
|||
candidate.count(piece_type) == valid_pieces[piece_type] |
|||
), f"piece type '{piece_type}' has invalid count" |
|||
bishops_pos = [index for index, |
|||
for «K Q» -> $one { |
|||
value in enumerate(candidate) if value == "B"] |
|||
if +$ascii.indices($one) != 1 { |
|||
assert ( |
|||
die "Illegal position: should have exactly one $one\n"; |
|||
bishops_pos[0] % 2 != bishops_pos[1] % 2 |
|||
} |
|||
), f"candidate position has both bishops in the same color" |
|||
} |
|||
assert [piece for piece in candidate if piece in "RK"] == [ |
|||
for «B N R» -> $two { |
|||
"R", |
|||
if +$ascii.indices($two) != 2 { |
|||
"K", |
|||
die "Illegal position: should have exactly two $two\'s\n"; |
|||
"R", |
|||
} |
|||
], "candidate position has K outside of RR" |
|||
} |
|||
if $ascii !~~ /'R' .* 'K' .* 'R'/ { |
|||
die "Illegal position: King not between rooks."; |
|||
} |
|||
def calc_position(start_pos: str): |
|||
if [+]($ascii.indices('B').map(* % 2)) != 1 { |
|||
try: |
|||
die "Illegal position: Bishops not on opposite colors."; |
|||
validate_position(start_pos) |
|||
} |
|||
except AssertionError: |
|||
# (end optional error-checking) |
|||
raise AssertionError |
|||
# step 1 |
|||
subset_step1 = [piece for piece in start_pos if piece not in "QB"] |
|||
nights_positions = [ |
|||
index for index, value in enumerate(subset_step1) if value == "N" |
|||
] |
|||
nights_table = { |
|||
(0, 1): 0, |
|||
(0, 2): 1, |
|||
(0, 3): 2, |
|||
(0, 4): 3, |
|||
(1, 2): 4, |
|||
(1, 3): 5, |
|||
(1, 4): 6, |
|||
(2, 3): 7, |
|||
(2, 4): 8, |
|||
(3, 4): 9, |
|||
} |
|||
N = nights_table.get(tuple(nights_positions)) |
|||
# step 2 |
|||
# Work backwards through the placement rules. |
|||
subset_step2 = [piece for piece in start_pos if piece != "B"] |
|||
# King and rooks are forced during placement, so ignore them. |
|||
Q = subset_step2.index("Q") |
|||
# step 3 |
|||
# 1. Figure out which knight combination was used: |
|||
dark_squares = [ |
|||
my @knights = $ascii |
|||
piece for index, piece in enumerate(start_pos) if index in range(0, 9, 2) |
|||
.subst(/<[QB]>/,'',:g) |
|||
] |
|||
.indices('N'); |
|||
light_squares = [ |
|||
piece for index, piece in enumerate(start_pos) if index in range(1, 9, 2) |
|||
] |
|||
D = dark_squares.index("B") |
|||
L = light_squares.index("B") |
|||
return 4 * (4 * (6*N + Q) + D) + L |
|||
my $knight = combinations(5,2).kv.grep( |
|||
-> $i,@c { @c eq @knights } |
|||
)[0][0]; |
|||
if __name__ == '__main__': |
|||
# 2. Then which queen position: |
|||
for example in ["QNRBBNKR", "RNBQKBNR", "RQNBBKRN", "RNQBBKRN"]: |
|||
my $queen = $ascii |
|||
print(f'Position: {example}; Chess960 PID= {calc_position(example)}')</syntaxhighlight> |
|||
.subst(/<[B]>/,'',:g) |
|||
{{out}} |
|||
.index('Q'); |
|||
<pre> |
|||
Position: QNRBBNKR; Chess960 PID= 105 |
|||
Position: RNBQKBNR; Chess960 PID= 518 |
|||
Position: RQNBBKRN; Chess960 PID= 601 |
|||
Position: RNQBBKRN; Chess960 PID= 617 |
|||
</pre> |
|||
=={{header|Raku}}== |
|||
# 3. Finally the two bishops: |
|||
<syntaxhighlight lang="raku" line>sub c960-spid($array) { |
|||
my @bishops = $ascii.indices('B'); |
|||
# standardize on letters for easier processing |
|||
my $dark = @bishops.grep({ $_ %% 2 })[0] div 2; |
|||
my $ascii = $array.trans('♜♞♝♛♚♖♘♗♕♔' => 'RNBQK'); |
|||
my $light = @bishops.grep({ not $_ %% 2 })[0] div 2; |
|||
# error-checking |
|||
my $sp-id = 4*(4*(6*$knight + $queen)+$dark)+$light; |
|||
my %Names = <Q Queen K King R Rook N Knight B Bishop>; |
|||
return 'Illegal position: should have exactly eight pieces' unless 8 == $ascii.chars; |
|||
return 'Illegal position: Bishops not on opposite colors.' unless 1 == sum $ascii.indices('B').map(* % 2); |
|||
return 'Illegal position: King not between rooks.' unless $ascii ~~ /'R' .* 'K' .* 'R'/; |
|||
for <K 1 Q 1 B 2 N 2 R 2> -> $piece, $count { |
|||
return "Illegal position: should have exactly $count %Names{$piece}\(s\)\n" unless $count == $ascii.indices($piece) |
|||
} |
|||
# Work backwards through the placement rules. |
|||
# standardize output |
|||
# King and rooks are forced during placement, so ignore them. |
|||
my $display = $ascii.trans("RNBQK" => "♖♘♗♕♔"); |
|||
# 1. Figure out which knight combination was used: |
|||
say "$display: $sp-id";</lang> |
|||
my @knights = $ascii.subst(/<[QB]>/, '', :g).indices('N'); |
|||
my $knight = combinations(5,2).kv.grep( -> $i, @c { @c eq @knights } ).flat.first; |
|||
# 2. Then which queen position: |
|||
{{Out}} |
|||
my $queen = $ascii.subst('B', '', :g).index('Q'); |
|||
<pre>$ c960spid QNRBBNKR |
|||
♕♘♖♗♗♘♔♖: 105 |
|||
# 3. Finally the two bishops: |
|||
$ c960spid |
|||
my @bishops = $ascii.indices('B'); |
|||
♖♘♗♕♔♗♘♖: 518</pre> |
|||
my ($dark,$light) = (@bishops.first %% 2 ?? @bishops !! @bishops.reverse) Xdiv 2; |
|||
$ascii.trans('RNBQK' => '♖♘♗♕♔') ~ ' ' ~ 4 × (4 × (6 × $knight + $queen) + $dark) + $light; |
|||
} |
|||
say .&c960-spid for <♖♘♗♕♔♗♘♖ ♛♞♜♝♝♞♚♜ RQNBBKRN RNQBBKRN QNBRBNKR>;</syntaxhighlight> |
|||
{{out}} |
|||
<pre>♖♘♗♕♔♗♘♖ 518 |
|||
♕♘♖♗♗♘♔♖ 105 |
|||
♖♕♘♗♗♔♖♘ 601 |
|||
♖♘♕♗♗♔♖♘ 617 |
|||
Illegal position: Bishops not on opposite colors.</pre> |
|||
=={{header|Ruby}}== |
|||
<syntaxhighlight lang="ruby">CHESS_PIECES = %w<♖♘♗♕♔ ♜♞♝♛♚> |
|||
def chess960_to_spid(pos) |
|||
start_str = pos.tr(CHESS_PIECES.join, "RNBQKRNBQK") |
|||
#1 knights score |
|||
s = start_str.delete("QB") |
|||
n = [0,1,2,3,4].combination(2).to_a.index( [s.index("N"), s.rindex("N")] ) |
|||
#2 queen score |
|||
q = start_str.delete("B").index("Q") |
|||
#3 bishops |
|||
bs = start_str.index("B"), start_str.rindex("B") |
|||
d = bs.detect(&:even?).div(2) |
|||
l = bs.detect(&:odd? ).div(2) |
|||
96*n + 16*q + 4*d + l |
|||
end |
|||
%w<QNRBBNKR RNBQKBNR RQNBBKRN RNQBBKRN>.each_with_index do |array, i| |
|||
pieces = array.tr("RNBQK", CHESS_PIECES[i%2]) |
|||
puts "#{pieces} (#{array}): #{chess960_to_spid array}" |
|||
end |
|||
</syntaxhighlight> |
|||
{{out}} |
|||
<pre>♕♘♖♗♗♘♔♖ (QNRBBNKR): 105 |
|||
♜♞♝♛♚♝♞♜ (RNBQKBNR): 518 |
|||
♖♕♘♗♗♔♖♘ (RQNBBKRN): 601 |
|||
♜♞♛♝♝♚♜♞ (RNQBBKRN): 617 |
|||
</pre> |
|||
=={{header|Wren}}== |
=={{header|Wren}}== |
||
{{libheader|Wren- |
{{libheader|Wren-iterate}} |
||
< |
<syntaxhighlight lang="wren">import "./iterate" for Indexed |
||
var glyphs = "♜♞♝♛♚♖♘♗♕♔".toList |
var glyphs = "♜♞♝♛♚♖♘♗♕♔".toList |
||
Line 373: | Line 1,152: | ||
var N = ntable[np] |
var N = ntable[np] |
||
var piecesQ = pieces.replace(" |
var piecesQ = pieces.replace("B", "") |
||
var Q = piecesQ.indexOf("Q") |
var Q = piecesQ.indexOf("Q") |
||
Line 386: | Line 1,165: | ||
} |
} |
||
for (pieces in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖"]) { |
for (pieces in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♜♛♞♝♝♚♜♞", "♜♞♛♝♝♚♜♞"]) { |
||
System.print("%(pieces) or %(g2l.call(pieces)) has SP-ID of %(spid.call(pieces))") |
System.print("%(pieces) or %(g2l.call(pieces)) has SP-ID of %(spid.call(pieces))") |
||
}</ |
}</syntaxhighlight> |
||
{{out}} |
{{out}} |
||
Line 394: | Line 1,173: | ||
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 |
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 |
||
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 |
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 |
||
♜♛♞♝♝♚♜♞ or RQNBBKRN has SP-ID of 601 |
|||
♜♞♛♝♝♚♜♞ or RNQBBKRN has SP-ID of 617 |
|||
</pre> |
</pre> |
Revision as of 10:37, 5 December 2023
You are encouraged to solve this task according to the task description, using any language you may know.
As described on the Chess960 page, Chess960 (a.k.a Fischer Random Chess, Chess9LX) is a variant of chess where the array of pieces behind the pawns is randomized at the start of the game to minimize the value of opening theory "book knowledge". That task is to generate legal starting positions, and some of the solutions accept a standard Starting Position Identifier number ("SP-ID"), and generate the corresponding position.
- Task
This task is to go the other way: given a starting array of pieces (provided in any form that suits your implementation, whether string or list or array, of letters or Unicode chess symbols or enum values, etc.), derive its unique SP-ID. For example, given the starting array QNRBBNKR (or ♕♘♖♗♗♘♔♖ or ♛♞♜♝♝♞♚♜), which we assume is given as seen from White's side of the board from left to right, your (sub)program should return 105; given the starting lineup of standard chess, it should return 518.
You may assume the input is a valid Chess960 position; detecting invalid input (including illegal characters or starting arrays with the bishops on the same color square or the king not between the two rooks) is optional.
- Algorithm
The derivation is the inverse of the algorithm given at Wikipedia, and goes like this (we'll use the standard chess setup as an example).
1. Ignoring the Queen and Bishops, find the positions of the Knights within the remaining five spaces (in the standard array they're in the second and fourth positions), and then find the index number of that combination. There's a table at the above Wikipedia article, but it's just the possible positions sorted left to right and numbered 0 to 9: 0=NN---, 1=N-N--, 2=N--N-, 3=N---N, 4=-NN--, etc; our pair is combination number 5. Call this number N. N=5
2. Still ignoring the Bishops, find the position of the Queen in the remaining 6 spaces; number them 0..5 from left to right and call the index of the Queen's position Q. In our example, Q=2.
3. Finally, find the positions of the two bishops within their respective sets of four like-colored squares. It's important to note here that the board in chess is placed such that the leftmost position on the home row is on a dark square and the rightmost a light. So if we number the squares of each color 0..3 from left to right, the dark bishop in the standard position is on square 1 (D=1), and the light bishop is on square 2 (L=2).
4. Then the position number is given by 4(4(6N + Q)+D)+L, which reduces to 96N + 16Q + 4D + L. In our example, that's 96×5 + 16×2 + 4×1 + 2 = 480 + 32 + 4 + 2 = 518.
Note that an earlier iteration of this page contained an incorrect description of the algorithm which would give the same SP-ID for both of the following two positions.
RQNBBKRN = 601 RNQBBKRN = 617
11l
F validate_position(String candidate)
assert(candidate.len == 8, ‘candidate position has invalid len = ’candidate.len)
V valid_pieces = [‘R’ = 2, ‘N’ = 2, ‘B’ = 2, ‘Q’ = 1, ‘K’ = 1]
assert(Set(Array(candidate)) == Set(valid_pieces.keys()), ‘candidate position contains invalid pieces’)
L(piece_type) valid_pieces.keys()
assert(candidate.count(piece_type) == valid_pieces[piece_type], ‘piece type '’piece_type‘' has invalid count’)
V bishops_pos = enumerate(Array(candidate)).filter((index, value) -> value == ‘B’).map((index, value) -> index)
assert(bishops_pos[0] % 2 != bishops_pos[1] % 2, ‘candidate position has both bishops in the same color’)
assert(candidate.filter(piece -> piece C ‘RK’) == [‘R’, ‘K’, ‘R’], ‘candidate position has K outside of RR’)
F calc_position(String start_pos)
validate_position(start_pos)
V subset_step1 = start_pos.filter(piece -> piece !C ‘QB’)
V nights_positions = enumerate(subset_step1).filter((index, value) -> value == ‘N’).map((index, value) -> index)
V nights_table = [(0, 1) = 0,
(0, 2) = 1,
(0, 3) = 2,
(0, 4) = 3,
(1, 2) = 4,
(1, 3) = 5,
(1, 4) = 6,
(2, 3) = 7,
(2, 4) = 8,
(3, 4) = 9]
V n = nights_table[(nights_positions[0], nights_positions[1])]
V subset_step2 = start_pos.filter(piece -> piece != ‘B’)
V q = subset_step2.index(‘Q’)
V dark_squares = enumerate(Array(start_pos)).filter((index, piece) -> index C Array((0.<9).step(2))).map((index, piece) -> piece)
V light_squares = enumerate(Array(start_pos)).filter((index, piece) -> index C Array((1.<9).step(2))).map((index, piece) -> piece)
V d = dark_squares.index(‘B’)
V l = light_squares.index(‘B’)
R 4 * (4 * (6*n + q) + d) + l
L(example) [‘QNRBBNKR’, ‘RNBQKBNR’, ‘RQNBBKRN’, ‘RNQBBKRN’]
print(‘Position: ’example‘; Chess960 PID= ’calc_position(example))
- Output:
Position: QNRBBNKR; Chess960 PID= 105 Position: RNBQKBNR; Chess960 PID= 518 Position: RQNBBKRN; Chess960 PID= 601 Position: RNQBBKRN; Chess960 PID= 617
BASIC
Commodore BASIC
Unlike the solution for the reverse task, which uses DO/LOOP and so requires at least Commodore BASIC 3.5, this should work on any version.
100 REM DERIVE SP-ID FROM CHESS960 POS
110 READ A$: IF A$="" THEN END
120 PRINT A$":";
130 GOSUB 170
140 PRINT SP
150 GOTO 110
160 DATA QNRBBNKR, RNBQKBNR, RQNBBKRN, RNQBBKRN,
170 IF LEN(A$)=8 THEN 190
180 PRINT "ARRAY MUST BE 8 PIECES.": SP=-1: RETURN
190 K=0:Q=0:B=0:N=0:R=0
200 FOR I=0 TO 7
210 : K(I)=0:Q(I)=0:B(I)=0:N(I)=0:R(I)=0
220 NEXT I
230 FOR I=1 TO 8
240 : P$=MID$(A$,I,1)
250 : IF P$="Q" THEN Q(Q)=I: Q=Q+1: GOTO 310
260 : IF P$="K" THEN K(K)=I: K=K+1: GOTO 310
270 : IF P$="B" THEN B(B)=I: B=B+1: GOTO 310
280 : IF P$="N" THEN N(N)=I: N=N+1: GOTO 310
290 : IF P$="R" THEN R(R)=I: R=R+1: GOTO 310
300 : PRINT "ILLEGAL PIECE '"P$"'.": SP=-1: RETURN
310 NEXT I
320 IF K<>1 THEN PRINT "THERE MUST BE EXACTLY ONE KING.": SP=-1: RETURN
330 IF Q<>1 THEN PRINT "THERE MUST BE EXACTLY ONE QUEEN.": SP=-1: RETURN
340 IF B<>2 THEN PRINT "THERE MUST BE EXACTLY TWO BISHOPS.": SP=-1: RETURN
350 IF N<>2 THEN PRINT "THERE MUST BE EXACTLY TWO KNIGHTS.": SP=-1: RETURN
360 IF R<>2 THEN PRINT "THERE MUST BE EXACTLY TWO ROOKS.": SP=-1: RETURN
370 IF (K(0) > R(0)) AND (K(0) < R(1)) THEN 390
380 PRINT "KING MUST BE BETWEEN THE ROOKS.": SP=-1: RETURN
390 IF (B(0) AND 1) <> (B(1) AND 1) THEN 410
400 PRINT "BISHOPS MUST BE ON OPPOSITE COLORS.": SP=-1: RETURN
410 FOR I=0 TO 1
420 : N=N(I)
430 : IF N(I)>Q(I) THEN N=N-1
440 : FOR J=0 TO 1
450 : IF N(I)>B(J) THEN N=N-1
460 : NEXT J
470 : N(I)=N
480 NEXT I
490 N0=1: N1=2
500 FOR N=0 TO 9
510 : IF N0=N(0) AND N1=N(1) THEN 550
520 : N1=N1+1
530 : IF N1>5 THEN N0=N0+1: N1=N0+1
540 NEXT N
550 Q=Q(0)-1
560 FOR I=0 TO 1
570 : IF Q(0)>B(I) THEN Q=Q-1
580 NEXT I
590 FOR I=0 TO 1
600 : B=B(I)-1
610 : IF B AND 1 THEN L=INT(B/2)
620 : IF (B AND 1)=0 THEN D=B/2
630 NEXT I
640 SP = 96*N+16*Q+4*D+L
650 RETURN
- Output:
READY. RUN QNRBBNKR: 105 RNBQKBNR: 518 RQNBBKRN: 601 RNQBBKRN: 617 READY.
FreeBASIC
Sub SP_ID(PosicPiezas As String)
Dim As String pieza
Dim As Integer pQ(), pK(), pB(), pN(), pR(), i, j
Dim As Integer Q, K, B, N, R, L, D
For i = 1 To 8
pieza = Mid(PosicPiezas, i, 1)
Select Case pieza
Case "Q"
Redim Preserve pQ(Q) : pQ(Q) = i: Q += 1
Case "K"
Redim Preserve pK(K) : pK(K) = i: K += 1
Case "B"
Redim Preserve pB(B) : pB(B) = i: B += 1
Case "N"
Redim Preserve pN(N) : pN(N) = i: N += 1
Case "R"
Redim Preserve pR(R) : pR(R) = i: R += 1
Case Else
Print "ILLEGAL PIECE '"; pieza; "'.": Exit Sub
End Select
Next i
If K <> 1 Then Print "THERE MUST BE EXACTLY ONE KING."
If Q <> 1 Then Print "THERE MUST BE EXACTLY ONE QUEEN."
If B <> 2 Then Print "THERE MUST BE EXACTLY TWO BISHOPS."
If N <> 2 Then Print "THERE MUST BE EXACTLY TWO KNIGHTS."
If R <> 2 Then Print "THERE MUST BE EXACTLY TWO ROOKS."
If Not (pK(0) > pR(0)) And (pK(0) < pR(1)) Then Print "KING MUST BE BETWEEN THE ROOKS."
If Not (pB(0) And 1) <> (pB(1) And 1) Then Print "BISHOPS MUST BE ON OPPOSITE COLORS."
For i = 0 To 1
N = pN(i)
If pN(i) > pQ(i) Then N -= 1
For j = 0 To 1
If pN(i) > pB(j) Then N -= 1
Next j
pN(i) = N
Next i
Dim As Integer N0 = 1, N1 = 2
For N = 0 To 9
If N0 = pN(0) And N1 = pN(1) Then Exit For
N1 += 1
If N1 > 5 Then N0 += 1: N1 = N0 + 1
Next N
Q = pQ(0) - 1
For i = 0 To 1
If pQ(0) > pB(i) Then Q -= 1
Next i
For i = 0 To 1
B = pB(i) - 1
If B And 1 Then L = Int(B / 2)
If (B And 1) = 0 Then D = B / 2
Next i
Print PosicPiezas; " has SP_ID of"; 96 * N + 16 * Q + 4 * D + L
End Sub
SP_ID("QNRBBNKR")
Print
SP_ID("RNBQKBNR")
Print
SP_ID("RQNBBKRN")
Print
SP_ID("RNQBBKRN")
Sleep
- Output:
QNRBBNKR has SP_ID of 105 RNBQKBNR has SP_ID of 518 RQNBBKRN has SP_ID of 601 RNQBBKRN has SP_ID of 617
QBasic
Cls
Print "Enter start array as seen by white."
120 Print
Print "Starting array";
Input Ar$
Print
If Len(Ar$) = 0 Then End
If Len(Ar$) = 8 Then 170
Print "Array must be 8 pieces.": GoTo 120
170 For I = 1 To 8
P$ = Mid$(Ar$, I, 1)
If P$ = "Q" Or P$ = "q" Then Q(Q) = I: Q = Q + 1: GoTo 250
If P$ = "K" Or P$ = "k" Then K(K) = I: K = K + 1: GoTo 250
If P$ = "B" Or P$ = "b" Then B(B) = I: B = B + 1: GoTo 250
If P$ = "N" Or P$ = "n" Then N(N) = I: N = N + 1: GoTo 250
If P$ = "R" Or P$ = "r" Then R(R) = I: R = R + 1: GoTo 250
Print "Illegal piece '"; P$; "'.": GoTo 120
250 Next I
If K <> 1 Then Print "There must be exactly one King.": GoTo 120
If Q <> 1 Then Print "There must be exactly one Queen.": GoTo 120
If B <> 2 Then Print "There must be exactly two Bishops.": GoTo 120
If N <> 2 Then Print "There must be exactly two Knights.": GoTo 120
If R <> 2 Then Print "There must be exactly two Rooks.": GoTo 120
If (K(0) > R(0)) And (K(0) < R(1)) Then 330
Print "King must be between the Rooks.": GoTo 120
330 If (B(0) And 1) <> (B(1) And 1) Then 350
Print "Bishops must be on opposite colors.": GoTo 120
350 For I = 0 To 1
N = N(I)
If N(I) > Q(I) Then N = N - 1
For J = 0 To 1
If N(I) > B(J) Then N = N - 1
Next J
N(I) = N
Next I
N0 = 1: N1 = 2
For N = 0 To 9
If N0 = N(0) And N1 = N(1) Then 490
N1 = N1 + 1
If N1 > 5 Then N0 = N0 + 1: N1 = N0 + 1
Next N
490 Q = Q(0) - 1
For I = 0 To 1
If Q(0) > B(I) Then Q = Q - 1
Next I
For I = 0 To 1
B = B(I) - 1
If B And 1 Then L = Int(B / 2)
If (B And 1) = 0 Then D = B / 2
Next I
Print "SP-ID ="; 96 * N + 16 * Q + 4 * D + L
End
- Output:
Enter start array as seen by White. Starting array? qnrbbnkr SP-ID = 105 Starting array? RNBQKBNR SP-ID = 518 Starting array? RQNBBKRN SP-ID = 601 Starting array? RNQBBKRN SP-ID = 617
C++
#include <algorithm>
#include <cstdint>
#include <stdexcept>
#include <iostream>
#include <string>
#include <map>
#include <set>
#include <vector>
const std::set<std::pair<char, int32_t>> correct_pieces = { std::make_pair('R', 2), std::make_pair('N', 2),
std::make_pair('B', 2), std::make_pair('Q', 1), std::make_pair('K', 1) };
std::map<std::vector<int32_t>, int32_t> knights_table = {
{ std::vector<int32_t>{ 0, 1 }, 0 },
{ std::vector<int32_t>{ 0, 2 }, 1 },
{ std::vector<int32_t>{ 0, 3 }, 2 },
{ std::vector<int32_t>{ 0, 4 }, 3 },
{ std::vector<int32_t>{ 1, 2 }, 4 },
{ std::vector<int32_t>{ 1, 3 }, 5 },
{ std::vector<int32_t>{ 1, 4 }, 6 },
{ std::vector<int32_t>{ 2, 3 }, 7 },
{ std::vector<int32_t>{ 2, 4 }, 8 },
{ std::vector<int32_t>{ 3, 4 }, 9 } };
void validate(const std::string& position) {
if ( position.length() != 8 ) {
throw std::invalid_argument("Chess position has invalid length" + std::to_string(position.length()));
}
std::map<char, int32_t> position_map;
for ( const char& ch : position ) {
if ( position_map.find(ch) == position_map.end() ) {
position_map.emplace(ch, 1);
} else {
position_map[ch]++;
}
}
std::set<std::pair<char, int32_t>> pieces;
std::transform(position_map.begin(), position_map.end(), std::inserter(pieces, pieces.begin()),
[](const std::pair<char, int32_t>& entry) { return entry; });
if ( pieces != correct_pieces ) {
throw std::invalid_argument("Chess position contains incorrect pieces.");
}
const std::vector<uint64_t> bishops = { position.find_first_of('B'), position.find_last_of('B') };
if ( ( bishops[1] - bishops[0] ) % 2 == 0 ) {
throw std::invalid_argument("Bishops must be on different coloured squares.");
}
std::vector<uint64_t> rook_king =
{ position.find_first_of('R'), position.find_first_of('K'), position.find_last_of('R') };
if ( ! ( rook_king[0] < rook_king[1] && rook_king[1] < rook_king[2] ) ) {
throw std::invalid_argument("The king must be between the two rooks.");
}
}
int32_t calculate_SPID(std::string& position) {
const int32_t index_one = position.find_first_of('B');
const int32_t index_two = position.find_last_of('B');
const int32_t D = ( index_one % 2 == 0 ) ? index_one / 2 : index_two / 2;
const int32_t L = ( index_one % 2 == 0 ) ? index_two / 2 : index_one / 2;
position.erase(remove_if(position.begin(), position.end(),
[](const char& ch){ return ch == 'B'; }), position.end());
const uint64_t Q = position.find_first_of('Q');
position.erase(remove_if(position.begin(), position.end(),
[](const char& ch){ return ch == 'Q'; }), position.end());
const int32_t N =
knights_table[ { (int32_t) position.find_first_of('N'), (int32_t) position.find_last_of('N') } ];
return 96 * N + 16 * Q + 4 * D + L;
}
int main() {
std::vector<std::string> positions = { "QNRBBNKR", "RNBQKBNR", "RQNBBKRN", "RNQBBKRN" };
for ( std::string& position : positions ) {
validate(position);
std::cout << "Position " << position << " has Chess960 SP-ID = " << calculate_SPID(position) << std::endl;
}
}
- Output:
Position QNRBBNKR has Chess960 SP-ID = 105 Position RNBQKBNR has Chess960 SP-ID = 518 Position RQNBBKRN has Chess960 SP-ID = 601 Position RNQBBKRN has Chess960 SP-ID = 617
Common Lisp
; make sure string is a valid Chess960 starting array
(defun valid-array-p (start-array)
(and (string-equal (sort (copy-seq start-array) #'string-lessp) "BBKNNQRR") ; right pieces
(not (equal (mod (position #\B start-array) 2) ; bishops on opposite colors
(mod (position #\B start-array :from-end t) 2)))
(< (position #\R start-array) (position #\K start-array)) ; king between two rooks
(< (position #\K start-array) (position #\R start-array :from-end t))))
; find Start Position IDentifier for a Chess960 setup
(defun sp-id (start-array)
(if (not (valid-array-p start-array))
-1
(let* ((bishopless (remove #\B start-array))
(queenless (remove #\Q bishopless))
(n5n-pattern (substitute-if-not #\- (lambda (ch) (eql ch #\N)) queenless))
(n5n-table '("NN---" "N-N--" "N--N-" "N---N" "-NN--" "-N-N-" "-N--N" "--NN-" "--N-N" "---NN"))
(knights (position n5n-pattern n5n-table :test #'string-equal))
(queen (position #\Q bishopless))
(left-bishop (position #\B start-array))
(right-bishop (position #\B start-array :from-end t)))
; map each bishop to its color complex and position within those four squares
(destructuring-bind (dark-bishop light-bishop)
(mapcar (lambda (p) (floor p 2))
(cond ((zerop (mod left-bishop 2)) (list left-bishop right-bishop))
(t (list right-bishop left-bishop))))
(+ (* 96 knights) (* 16 queen) (* 4 dark-bishop) light-bishop)))))
(loop for ary in '("RNBQKBNR" "QNRBBNKR" "RQNBBKRN" "RNQBBKRN") doing
(format t "~a: ~a~%" ary (sp-id ary)))
- Output:
RNBQKBNR: 518 QNRBBNKR: 105 RQNBBKRN: 601 RNQBBKRN: 617
Factor
USING: assocs assocs.extras combinators formatting kernel
literals math math.combinatorics sequences sequences.extras sets
strings ;
IN: scratchpad
! ====== optional error-checking ======
: check-length ( str -- )
length 8 = [ "Must have 8 pieces." throw ] unless ;
: check-one ( str -- )
"KQ" counts [ nip 1 = not ] assoc-find nip
[ 1string "Must have one %s." sprintf throw ] [ drop ] if ;
: check-two ( str -- )
"BNR" counts [ nip 2 = not ] assoc-find nip
[ 1string "Must have two %s." sprintf throw ] [ drop ] if ;
: check-king ( str -- )
"QBN" without "RKR" =
[ "King must be between rooks." throw ] unless ;
: check-bishops ( str -- )
CHAR: B swap indices sum odd?
[ "Bishops must be on opposite colors." throw ] unless ;
: check-sp ( str -- )
{
[ check-length ]
[ check-one ]
[ check-two ]
[ check-king ]
[ check-bishops ]
} cleave ;
! ====== end optional error-checking ======
CONSTANT: convert $[ "RNBQK" "♖♘♗♕♔" zip ]
CONSTANT: table $[ "NN---" all-unique-permutations ]
: knightify ( str -- newstr )
[ dup CHAR: N = [ drop CHAR: - ] unless ] map ;
: n ( str -- n ) "QB" without knightify table index ;
: q ( str -- q ) "B" without CHAR: Q swap index ;
: d ( str -- d ) CHAR: B swap <evens> index ;
: l ( str -- l ) CHAR: B swap <odds> index ;
: sp-id ( str -- n )
dup check-sp
{ [ n 96 * ] [ q 16 * + ] [ d 4 * + ] [ l + ] } cleave ;
: sp-id. ( str -- )
dup [ convert substitute ] [ sp-id ] bi
"%s / %s: %d\n" printf ;
"QNRBBNKR" sp-id.
"RNBQKBNR" sp-id.
"RQNBBKRN" sp-id.
"RNQBBKRN" sp-id.
- Output:
QNRBBNKR / ♕♘♖♗♗♘♔♖: 105 RNBQKBNR / ♖♘♗♕♔♗♘♖: 518 RQNBBKRN / ♖♕♘♗♗♔♖♘: 601 RNQBBKRN / ♖♘♕♗♗♔♖♘: 617
Go
package main
import (
"fmt"
"log"
"strings"
)
var glyphs = []rune("♜♞♝♛♚♖♘♗♕♔")
var names = map[rune]string{'R': "rook", 'N': "knight", 'B': "bishop", 'Q': "queen", 'K': "king"}
var g2lMap = map[rune]string{
'♜': "R", '♞': "N", '♝': "B", '♛': "Q", '♚': "K",
'♖': "R", '♘': "N", '♗': "B", '♕': "Q", '♔': "K",
}
var ntable = map[string]int{"01": 0, "02": 1, "03": 2, "04": 3, "12": 4, "13": 5, "14": 6, "23": 7, "24": 8, "34": 9}
func g2l(pieces string) string {
lets := ""
for _, p := range pieces {
lets += g2lMap[p]
}
return lets
}
func spid(pieces string) int {
pieces = g2l(pieces) // convert glyphs to letters
/* check for errors */
if len(pieces) != 8 {
log.Fatal("There must be exactly 8 pieces.")
}
for _, one := range "KQ" {
count := 0
for _, p := range pieces {
if p == one {
count++
}
}
if count != 1 {
log.Fatalf("There must be one %s.", names[one])
}
}
for _, two := range "RNB" {
count := 0
for _, p := range pieces {
if p == two {
count++
}
}
if count != 2 {
log.Fatalf("There must be two %s.", names[two])
}
}
r1 := strings.Index(pieces, "R")
r2 := strings.Index(pieces[r1+1:], "R") + r1 + 1
k := strings.Index(pieces, "K")
if k < r1 || k > r2 {
log.Fatal("The king must be between the rooks.")
}
b1 := strings.Index(pieces, "B")
b2 := strings.Index(pieces[b1+1:], "B") + b1 + 1
if (b2-b1)%2 == 0 {
log.Fatal("The bishops must be on opposite color squares.")
}
/* compute SP_ID */
piecesN := strings.ReplaceAll(pieces, "Q", "")
piecesN = strings.ReplaceAll(piecesN, "B", "")
n1 := strings.Index(piecesN, "N")
n2 := strings.Index(piecesN[n1+1:], "N") + n1 + 1
np := fmt.Sprintf("%d%d", n1, n2)
N := ntable[np]
piecesQ := strings.ReplaceAll(pieces, "B", "")
Q := strings.Index(piecesQ, "Q")
D := strings.Index("0246", fmt.Sprintf("%d", b1))
L := strings.Index("1357", fmt.Sprintf("%d", b2))
if D == -1 {
D = strings.Index("0246", fmt.Sprintf("%d", b2))
L = strings.Index("1357", fmt.Sprintf("%d", b1))
}
return 96*N + 16*Q + 4*D + L
}
func main() {
for _, pieces := range []string{"♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♖♕♘♗♗♔♖♘", "♖♘♕♗♗♔♖♘"} {
fmt.Printf("%s or %s has SP-ID of %d\n", pieces, g2l(pieces), spid(pieces))
}
}
- Output:
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 ♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 ♖♕♘♗♗♔♖♘ or RQNBBKRN has SP-ID of 601 ♖♘♕♗♗♔♖♘ or RNQBBKRN has SP-ID of 617
J
Implementation:
REF=: {{
'N Q B0 B1'=. 0 6 4 4 #: y
s=. 'B' (0 1+2*B0,B1)} 8#' '
s=. 'Q' (Q{I.' '=s)} s
s=. 'N' ((N{(#~ 2=+/"1)#:i.-32){&I.' '=s)} s
'RKR' (I.' '=s)} s
}}"0 i.960
c960=: {{ r=. REF i. rplc&((u:9812+i.12);&>12$'KQRBNP') 7 u:deb y assert. r<#REF }}
Examples:
c960'♕♘♖♗♗♘♔♖'
105
c960'♛♞♜♝♝♞♚♜'
105
c960'RNBQKBNR'
518
c960'RQNBBKRN'
601
c960'RNQBBKRN'
617
Java
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
public final class Chess960SPID {
public static void main(String[] aArgs) {
String[] positions = { "QNRBBNKR", "RNBQKBNR", "RQNBBKRN", "RNQBBKRN" };
createKnightsTable();
createCorrectPieces();
for ( String position : positions ) {
validate(position);
System.out.println("Position " + position + " has Chess960 SP-ID = " + calculateSPID(position));
}
}
private static void validate(String aPosition) {
if ( aPosition.length() != 8 ) {
throw new AssertionError("Chess position has invalid length: " + aPosition.length() + ".");
}
Map<Character, Integer> pieces = new HashMap<Character, Integer>();
for ( char ch : aPosition.toCharArray() ) {
pieces.merge(ch, 1, (oldV, newV) -> oldV + 1);
}
if ( ! pieces.entrySet().equals(correctPieces) ) {
throw new AssertionError("Chess position contains incorrect pieces.");
}
List<Integer> bishops = List.of(aPosition.indexOf('B'), aPosition.lastIndexOf('B'));
if ( ( bishops.get(1) - bishops.get(0) ) % 2 == 0 ) {
throw new AssertionError("Bishops must be on different coloured squares.");
}
List<Integer> rookKing = List.of(aPosition.indexOf('R'), aPosition.indexOf('K'), aPosition.lastIndexOf('R'));
if ( ! ( rookKing.get(0) < rookKing.get(1) && rookKing.get(1) < rookKing.get(2) ) ) {
throw new AssertionError("The king must be between the two rooks.");
}
}
private static int calculateSPID(String aPosition) {
String noBishopsOrQueen = retainIf(aPosition, s -> s != 'B' && s != 'Q');
final int N = knightsTable.get(List.of(noBishopsOrQueen.indexOf('N'), noBishopsOrQueen.lastIndexOf('N')));
String noBishops = retainIf(aPosition, s -> s != 'B');
final int Q = noBishops.indexOf('Q');
final int indexOne = aPosition.indexOf('B');
final int indexTwo = aPosition.lastIndexOf('B');
final int D = ( indexOne % 2 == 0 ) ? indexOne / 2 : indexTwo / 2;
final int L = ( indexOne % 2 == 0 ) ? indexTwo / 2 : indexOne / 2;
return 96 * N + 16 * Q + 4 * D + L;
}
private static String retainIf(String aText, Predicate<Character> aPredicate) {
return aText.chars()
.mapToObj( i -> (char) i )
.filter(aPredicate)
.map(String::valueOf)
.reduce("", String::concat);
}
private static void createKnightsTable() {
knightsTable = new HashMap<List<Integer>, Integer>();
knightsTable.put(List.of(0, 1), 0);
knightsTable.put(List.of(0, 2), 1);
knightsTable.put(List.of(0, 3), 2);
knightsTable.put(List.of(0, 4), 3);
knightsTable.put(List.of(1, 2), 4);
knightsTable.put(List.of(1, 3), 5);
knightsTable.put(List.of(1, 4), 6);
knightsTable.put(List.of(2, 3), 7);
knightsTable.put(List.of(2, 4), 8);
knightsTable.put(List.of(3, 4), 9);
}
private static void createCorrectPieces() {
correctPieces = Set.of(
Map.entry('R', 2), Map.entry('N', 2), Map.entry('B', 2), Map.entry('Q', 1), Map.entry('K', 1) );
}
private static Map<List<Integer>, Integer> knightsTable;
private static Set<Map.Entry<Character, Integer>> correctPieces;
}
- Output:
Position QNRBBNKR has Chess960 SP-ID = 105 Position RNBQKBNR has Chess960 SP-ID = 518 Position RQNBBKRN has Chess960 SP-ID = 601 Position RNQBBKRN has Chess960 SP-ID = 617
Julia
const whitepieces = "♖♘♗♕♔♗♘♖♙"
const whitechars = "rnbqkp"
const blackpieces = "♜♞♝♛♚♝♞♜♟"
const blackchars = "RNBQKP"
const piece2ascii = Dict(zip("♖♘♗♕♔♗♘♖♙♜♞♝♛♚♝♞♜♟", "rnbqkbnrpRNBQKBNRP"))
""" Derive a chess960 position's SP-ID from its string representation. """
function chess960spid(position::String = "♖♘♗♕♔♗♘♖", errorchecking = true)
if errorchecking
@assert length(position) == 8 "Need exactly 8 pieces"
@assert all(p -> p in whitepieces || p in blackpieces, position) "Invalid piece character"
@assert all(p -> p in whitepieces, position) || all(p -> p in blackpieces, position) "Side of pieces is mixed"
@assert all(p -> !(p in "♙♟"), position) "No pawns allowed"
end
a = uppercase(String([piece2ascii[c] for c in position]))
if errorchecking
@assert all(p -> count(x -> x == p, a) == 1, "KQ") "Need exactly one of each K and Q"
@assert all(p -> count(x -> x == p, a) == 2, "RNB") "Need exactly 2 of each R, N, B"
@assert findfirst(p -> p == 'R', a) < findfirst(p -> p == 'K', a) < findlast(p -> p == 'R', a) "King must be between rooks"
@assert isodd(findfirst(p -> p == 'B', a) + findlast(p -> p == 'B', a)) "Bishops must be on different colors"
end
knighttable = [12, 13, 14, 15, 23, 24, 25, 34, 35, 45]
noQB = replace(a, r"[QB]" => "")
knightpos1, knightpos2 = findfirst(c -> c =='N', noQB), findlast(c -> c =='N', noQB)
N = findfirst(s -> s == 10 * knightpos1 + knightpos2, knighttable) - 1
Q = findfirst(c -> c == 'Q', replace(a, "B" => "")) - 1
bishoppositions = [findfirst(c -> c =='B', a), findlast(c -> c =='B', a)]
if isodd(bishoppositions[2])
bishoppositions = reverse(bishoppositions) # dark color bishop first
end
D, L = bishoppositions[1] ÷ 2, bishoppositions[2] ÷ 2 - 1
return 96N + 16Q + 4D + L
end
for position in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♖♕♘♗♗♔♖♘", "♖♘♕♗♗♔♖♘"]
println(collect(position), " => ", chess960spid(position))
end
- Output:
['♕', '♘', '♖', '♗', '♗', '♘', '♔', '♖'] => 105 ['♖', '♘', '♗', '♕', '♔', '♗', '♘', '♖'] => 518 ['♖', '♕', '♘', '♗', '♗', '♔', '♖', '♘'] => 601 ['♖', '♘', '♕', '♗', '♗', '♔', '♖', '♘'] => 617
Nim
import sequtils, strformat, strutils, sugar, tables, unicode
type Piece {.pure.} = enum Rook = "R", Knight = "N", Bishop = "B", Queen = "Q", King = "K"
const
GlypthToPieces = {"♜": Rook, "♞": Knight, "♝": Bishop, "♛": Queen, "♚": King,
"♖": Rook, "♘": Knight, "♗": Bishop, "♕": Queen, "♔": King}.toTable
Names = [Rook: "rook", Knight: "knight", Bishop: "bishop", Queen: "queen", King: "king"]
NTable = {[0, 1]: 0, [0, 2]: 1, [0, 3]: 2, [0, 4]: 3, [1, 2]: 4,
[1, 3]: 5, [1, 4]: 6, [2, 3]: 7, [2, 4]: 8, [3, 4]: 9}.toTable
func toPieces(glyphs: string): seq[Piece] =
collect(newSeq, for glyph in glyphs.runes: GlypthToPieces[glyph.toUTF8])
func isEven(n: int): bool = (n and 1) == 0
func positions(pieces: seq[Piece]; piece: Piece): array[2, int] =
var idx = 0
for i, p in pieces:
if p == piece:
result[idx] = i
inc idx
func spid(glyphs: string): int =
let pieces = glyphs.toPieces()
# Check for errors.
if pieces.len != 8:
raise newException(ValueError, "there must be exactly 8 pieces.")
for piece in [King, Queen]:
if pieces.count(piece) != 1:
raise newException(ValueError, &"there must be one {Names[piece]}.")
for piece in [Rook, Knight, Bishop]:
if pieces.count(piece) != 2:
raise newException(ValueError, &"there must be two {Names[piece]}s.")
let r = pieces.positions(Rook)
let k = pieces.find(King)
if k < r[0] or k > r[1]:
raise newException(ValueError, "the king must be between the rooks.")
var b = pieces.positions(Bishop)
if isEven(b[1] - b[0]):
raise newException(ValueError, "the bishops must be on opposite color squares.")
# Compute SP_ID.
let piecesN = pieces.filterIt(it notin [Queen, Bishop])
let n = NTable[piecesN.positions(Knight)]
let piecesQ = pieces.filterIt(it != Bishop)
let q = piecesQ.find(Queen)
if b[1].isEven: swap b[0], b[1]
let d = [0, 2, 4, 6].find(b[0])
let l = [1, 3, 5, 7].find(b[1])
result = 96 * n + 16 * q + 4 * d + l
for glyphs in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♖♕♘♗♗♔♖♘", "♖♘♕♗♗♔♖♘"]:
echo &"{glyphs} or {glyphs.toPieces().join()} has SP-ID of {glyphs.spid()}"
- Output:
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 ♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 ♖♕♘♗♗♔♖♘ or RQNBBKRN has SP-ID of 601 ♖♘♕♗♗♔♖♘ or RNQBBKRN has SP-ID of 617
Perl
use v5.36;
use List::AllUtils 'indexes';
sub sp_id ($setup) {
8 == length $setup or return 'Illegal position: should have exactly eight pieces';
1 == @{[ $setup =~ /$_/g ]} or return "Illegal position: should have exactly one $_" for <K Q>;
2 == @{[ $setup =~ /$_/g ]} or return "Illegal position: should have exactly two $_\'s" for <B N R>;
$setup =~ m/R .* K .* R/x or return 'Illegal position: King not between rooks.';
index($setup,'B')%2 != rindex($setup,'B')%2 or return 'Illegal position: Bishops not on opposite colors.';
my @knights = indexes { 'N' eq $_ } split '', $setup =~ s/[QB]//gr;
my $knight = indexes { join('', @knights) eq $_ } <01 02 03 04 12 13 14 23 24 34>; # combinations(5,2)
my @bishops = indexes { 'B' eq $_ } split '', $setup;
my $dark = int ((grep { $_ % 2 == 0 } @bishops)[0]) / 2;
my $light = int ((grep { $_ % 2 == 1 } @bishops)[0]) / 2;
my $queen = index(($setup =~ s/B//gr), 'Q');
int 4*(4*(6*$knight + $queen)+$dark)+$light;
}
say "$_ " . sp_id($_) for <QNRBBNKR RNBQKBNR RQNBBKRN RNQBBKRN QNBRBNKR>;
- Output:
QNRBBNKR 105 RNBQKBNR 518 RQNBBKRN 601 RNQBBKRN 617 QNBRBNKR Illegal position: Bishops not on opposite colors.
Phix
with javascript_semantics function spid(string s) if sort(s)!="BBKNNQRR" then return -1 end if if filter(s,"in","RK")!="RKR" then return -1 end if sequence b = find_all('B',s) if even(sum(b)) then return -1 end if integer {n1,n2} = find_all('N',filter(s,"out","QB")), N = {-2,1,3,4}[n1]+n2, Q = find('Q',filter(s,"!=",'B'))-1, D = filter(b,odd)[1]-1, -- (nb not /2) L = filter(b,even)[1]/2-1 return 96*N + 16*Q + 2*D + L end function procedure test(string s) printf(1,"%s : %d\n",{s,spid(s)}) end procedure test("QNRBBNKR") test("RNBQKBNR") test("RQNBBKRN") test("RNQBBKRN")
- Output:
QNRBBNKR : 105 RNBQKBNR : 518 RQNBBKRN : 601 RNQBBKRN : 617
To support all those crazy unicode characters just change the start of spid() to:
function spid(string u) sequence u32 = utf8_to_utf32(u), c32 = utf8_to_utf32("♜♞♝♛♚♖♘♗♕♔"), s32 = substitute_all(u32,c32,"RNBQKRNBQK") string s = utf32_to_utf8(s32) --... and add: test("♕♘♖♗♗♘♔♖") test("♖♘♗♕♔♗♘♖") test("♜♛♞♝♝♚♜♞") test("♜♞♛♝♝♚♜♞")
- Output:
Note that output on a windows terminal is as expected far from pretty, this is from pwa/p2js
QNRBBNKR : 105 RNBQKBNR : 518 RQNBBKRN : 601 RNQBBKRN : 617 ♕♘♖♗♗♘♔♖ : 105 ♖♘♗♕♔♗♘♖ : 518 ♜♛♞♝♝♚♜♞ : 601 ♜♞♛♝♝♚♜♞ : 617
Python
# optional, but task function depends on it as written
def validate_position(candidate: str):
assert (
len(candidate) == 8
), f"candidate position has invalide len = {len(candidate)}"
valid_pieces = {"R": 2, "N": 2, "B": 2, "Q": 1, "K": 1}
assert {
piece for piece in candidate
} == valid_pieces.keys(), f"candidate position contains invalid pieces"
for piece_type in valid_pieces.keys():
assert (
candidate.count(piece_type) == valid_pieces[piece_type]
), f"piece type '{piece_type}' has invalid count"
bishops_pos = [index for index,
value in enumerate(candidate) if value == "B"]
assert (
bishops_pos[0] % 2 != bishops_pos[1] % 2
), f"candidate position has both bishops in the same color"
assert [piece for piece in candidate if piece in "RK"] == [
"R",
"K",
"R",
], "candidate position has K outside of RR"
def calc_position(start_pos: str):
try:
validate_position(start_pos)
except AssertionError:
raise AssertionError
# step 1
subset_step1 = [piece for piece in start_pos if piece not in "QB"]
nights_positions = [
index for index, value in enumerate(subset_step1) if value == "N"
]
nights_table = {
(0, 1): 0,
(0, 2): 1,
(0, 3): 2,
(0, 4): 3,
(1, 2): 4,
(1, 3): 5,
(1, 4): 6,
(2, 3): 7,
(2, 4): 8,
(3, 4): 9,
}
N = nights_table.get(tuple(nights_positions))
# step 2
subset_step2 = [piece for piece in start_pos if piece != "B"]
Q = subset_step2.index("Q")
# step 3
dark_squares = [
piece for index, piece in enumerate(start_pos) if index in range(0, 9, 2)
]
light_squares = [
piece for index, piece in enumerate(start_pos) if index in range(1, 9, 2)
]
D = dark_squares.index("B")
L = light_squares.index("B")
return 4 * (4 * (6*N + Q) + D) + L
if __name__ == '__main__':
for example in ["QNRBBNKR", "RNBQKBNR", "RQNBBKRN", "RNQBBKRN"]:
print(f'Position: {example}; Chess960 PID= {calc_position(example)}')
- Output:
Position: QNRBBNKR; Chess960 PID= 105 Position: RNBQKBNR; Chess960 PID= 518 Position: RQNBBKRN; Chess960 PID= 601 Position: RNQBBKRN; Chess960 PID= 617
Raku
sub c960-spid($array) {
# standardize on letters for easier processing
my $ascii = $array.trans('♜♞♝♛♚♖♘♗♕♔' => 'RNBQK');
# error-checking
my %Names = <Q Queen K King R Rook N Knight B Bishop>;
return 'Illegal position: should have exactly eight pieces' unless 8 == $ascii.chars;
return 'Illegal position: Bishops not on opposite colors.' unless 1 == sum $ascii.indices('B').map(* % 2);
return 'Illegal position: King not between rooks.' unless $ascii ~~ /'R' .* 'K' .* 'R'/;
for <K 1 Q 1 B 2 N 2 R 2> -> $piece, $count {
return "Illegal position: should have exactly $count %Names{$piece}\(s\)\n" unless $count == $ascii.indices($piece)
}
# Work backwards through the placement rules.
# King and rooks are forced during placement, so ignore them.
# 1. Figure out which knight combination was used:
my @knights = $ascii.subst(/<[QB]>/, '', :g).indices('N');
my $knight = combinations(5,2).kv.grep( -> $i, @c { @c eq @knights } ).flat.first;
# 2. Then which queen position:
my $queen = $ascii.subst('B', '', :g).index('Q');
# 3. Finally the two bishops:
my @bishops = $ascii.indices('B');
my ($dark,$light) = (@bishops.first %% 2 ?? @bishops !! @bishops.reverse) Xdiv 2;
$ascii.trans('RNBQK' => '♖♘♗♕♔') ~ ' ' ~ 4 × (4 × (6 × $knight + $queen) + $dark) + $light;
}
say .&c960-spid for <♖♘♗♕♔♗♘♖ ♛♞♜♝♝♞♚♜ RQNBBKRN RNQBBKRN QNBRBNKR>;
- Output:
♖♘♗♕♔♗♘♖ 518 ♕♘♖♗♗♘♔♖ 105 ♖♕♘♗♗♔♖♘ 601 ♖♘♕♗♗♔♖♘ 617 Illegal position: Bishops not on opposite colors.
Ruby
CHESS_PIECES = %w<♖♘♗♕♔ ♜♞♝♛♚>
def chess960_to_spid(pos)
start_str = pos.tr(CHESS_PIECES.join, "RNBQKRNBQK")
#1 knights score
s = start_str.delete("QB")
n = [0,1,2,3,4].combination(2).to_a.index( [s.index("N"), s.rindex("N")] )
#2 queen score
q = start_str.delete("B").index("Q")
#3 bishops
bs = start_str.index("B"), start_str.rindex("B")
d = bs.detect(&:even?).div(2)
l = bs.detect(&:odd? ).div(2)
96*n + 16*q + 4*d + l
end
%w<QNRBBNKR RNBQKBNR RQNBBKRN RNQBBKRN>.each_with_index do |array, i|
pieces = array.tr("RNBQK", CHESS_PIECES[i%2])
puts "#{pieces} (#{array}): #{chess960_to_spid array}"
end
- Output:
♕♘♖♗♗♘♔♖ (QNRBBNKR): 105 ♜♞♝♛♚♝♞♜ (RNBQKBNR): 518 ♖♕♘♗♗♔♖♘ (RQNBBKRN): 601 ♜♞♛♝♝♚♜♞ (RNQBBKRN): 617
Wren
import "./iterate" for Indexed
var glyphs = "♜♞♝♛♚♖♘♗♕♔".toList
var letters = "RNBQKRNBQK"
var names = { "R": "rook", "N": "knight", "B": "bishop", "Q": "queen", "K": "king" }
var g2lMap = {}
for (se in Indexed.new(glyphs)) g2lMap[glyphs[se.index]] = letters[se.index]
var g2l = Fn.new { |pieces| pieces.reduce("") { |acc, p| acc + g2lMap[p] } }
var ntable = { "01":0, "02":1, "03":2, "04":3, "12":4, "13":5, "14":6, "23":7, "24":8, "34":9 }
var spid = Fn.new { |pieces|
pieces = g2l.call(pieces) // convert glyphs to letters
/* check for errors */
if (pieces.count != 8) Fiber.abort("There must be exactly 8 pieces.")
for (one in "KQ") {
if (pieces.count { |p| p == one } != 1 ) Fiber.abort("There must be one %(names[one]).")
}
for (two in "RNB") {
if (pieces.count { |p| p == two } != 2 ) Fiber.abort("There must be two %(names[two])s.")
}
var r1 = pieces.indexOf("R")
var r2 = pieces.indexOf("R", r1 + 1)
var k = pieces.indexOf("K")
if (k < r1 || k > r2) Fiber.abort("The king must be between the rooks.")
var b1 = pieces.indexOf("B")
var b2 = pieces.indexOf("B", b1 + 1)
if ((b2 - b1) % 2 == 0) Fiber.abort("The bishops must be on opposite color squares.")
/* compute SP_ID */
var piecesN = pieces.replace("Q", "").replace("B", "")
var n1 = piecesN.indexOf("N")
var n2 = piecesN.indexOf("N", n1 + 1)
var np = "%(n1)%(n2)"
var N = ntable[np]
var piecesQ = pieces.replace("B", "")
var Q = piecesQ.indexOf("Q")
var D = "0246".indexOf(b1.toString)
var L = "1357".indexOf(b2.toString)
if (D == -1) {
D = "0246".indexOf(b2.toString)
L = "1357".indexOf(b1.toString)
}
return 96*N + 16*Q + 4*D + L
}
for (pieces in ["♕♘♖♗♗♘♔♖", "♖♘♗♕♔♗♘♖", "♜♛♞♝♝♚♜♞", "♜♞♛♝♝♚♜♞"]) {
System.print("%(pieces) or %(g2l.call(pieces)) has SP-ID of %(spid.call(pieces))")
}
- Output:
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105 ♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518 ♜♛♞♝♝♚♜♞ or RQNBBKRN has SP-ID of 601 ♜♞♛♝♝♚♜♞ or RNQBBKRN has SP-ID of 617