Find Chess960 starting position identifier

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.

Last updated