Find Chess960 starting position identifier: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|Raku}}: Changed <lang> to perl6 to get syntax highlighting.)
Line 89: Line 89:
RNBQKBNR / ♖♘♗♕♔♗♘♖: 518
RNBQKBNR / ♖♘♗♕♔♗♘♖: 518
</pre>
</pre>

=={{header|Nim}}==
{{trans|Wren}}
<lang 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 != Knight)
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()}"</lang>

{{out}}
<pre>♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518</pre>


=={{header|Raku}}==
=={{header|Raku}}==

Revision as of 19:44, 12 September 2021

Task
Find Chess960 starting position identifier
You are encouraged to solve this task according to the task description, using any language you may know.

As described on the Chess960 task, 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 ♛♞♜♝♝♞♚♜), 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. Now ignoring the Knights (but including the Queen and 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.

Factor

Works with: Factor version 0.99 2021-06-02

<lang factor>USING: assocs assocs.extras combinators formatting kernel literals math math.combinatorics sequences sequences.extras sets strings ;


! ====== 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 ) "N" 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.</lang>

Output:
QNRBBNKR / ♕♘♖♗♗♘♔♖: 105
RNBQKBNR / ♖♘♗♕♔♗♘♖: 518

Nim

Translation of: Wren

<lang 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 != Knight)
 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()}"</lang>
Output:
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518

Raku

<lang perl6>#!/usr/bin/env raku

  1. derive a chess960 position's SP-ID

unit sub MAIN($array = "♖♘♗♕♔♗♘♖");

  1. standardize on letters for easier processing

my $ascii = $array.trans("♜♞♝♛♚♖♘♗♕♔" => "RNBQKRNBQK");

  1. (optional error-checking)

if $ascii.chars != 8 {

   die "Illegal position: should have exactly eight pieces\n";

}

for «K Q» -> $one {

 if +$ascii.indices($one) != 1 {
   die "Illegal position: should have exactly one $one\n";
 }

}

for «B N R» -> $two {

 if +$ascii.indices($two) != 2 {
   die "Illegal position: should have exactly two $two\'s\n";
 }

}

if $ascii !~~ /'R' .* 'K' .* 'R'/ {

 die "Illegal position: King not between rooks.";

}

if [+]($ascii.indices('B').map(* % 2)) != 1 {

 die "Illegal position: Bishops not on opposite colors.";

}

  1. (end optional error-checking)
  1. Work backwards through the placement rules.
  2. King and rooks are forced during placement, so ignore them.
  1. 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 }
 )[0][0]; 
  1. 2. Then which queen position:

my $queen = $ascii

 .subst(/<[B]>/,,:g)
 .index('Q');
  1. 3. Finally the two bishops:

my @bishops = $ascii.indices('B'); my $dark = @bishops.grep({ $_ % 2 == 0 })[0] div 2; my $light = @bishops.grep({ $_ % 2 == 1 })[0] div 2;

my $sp-id = 4*(4*(6*$knight + $queen)+$dark)+$light;

  1. standardize output

my $display = $ascii.trans("RNBQK" => "♖♘♗♕♔");

say "$display: $sp-id";</lang>

Output:
$ c960spid QNRBBNKR
♕♘♖♗♗♘♔♖: 105
$ c960spid
♖♘♗♕♔♗♘♖: 518

Wren

Library: Wren-trait

<lang ecmascript>import "/trait" 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("N", "")
   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))")

}</lang>

Output:
♕♘♖♗♗♘♔♖ or QNRBBNKR has SP-ID of 105
♖♘♗♕♔♗♘♖ or RNBQKBNR has SP-ID of 518