This task has been flagged for clarification due to it being believed to be too difficult to implement in a reasonable amount of effort in more than one (usually very specialised) language. It may need to be divided into multiple tasks or modified substantially so that multiple implementations are practical, and that may cause code on this page in its current state to be flagged incorrect afterwards. See this page's Talk page for discussion.

In the early times, chess used to be the prime example of artificial intelligence. Nowadays, some chess programs can beat a human master, and simple implementations can be written in a few pages of code.

Chess player is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Write a program which plays chess against a human player.
No need for graphics -- a textual user interface is sufficient.

Rather than implementing a complete monolithic program, you may wish to tackle one of the simpler sub-tasks:

  1. Chess player/Move generation
  2. Chess player/Search and evaluation
  3. Chess player/Program options and user interface

or use those components as part of a complete program, demonstrating your language's support for modularity.

Tony Marsland's computer chess player Awit is written in ALGOL W.

Works with: QBasic

El código es de Dean Menezes

Encontrado en: http://www.petesqbsite.com/sections/express/issue23/Tut_QB_Chess.txt

DEFINT A-Z

DECLARE SUB SQUARE (A, B, C)
DECLARE SUB SHOWMAN (A, B, FLAG)
DECLARE SUB SHOWBD ()
DECLARE SUB IO (A, B, X, Y, RESULT)
DECLARE FUNCTION INCHECK (X)
DECLARE SUB MAKEMOVE (A, B, X, Y)
DECLARE SUB KNIGHT (A, B, XX(), YY(), NDX)
DECLARE SUB KING (A, B, XX(), YY(), NDX)
DECLARE SUB QUEEN (A, B, XX(), YY(), NDX)
DECLARE SUB ROOK (A, B, XX(), YY(), NDX)
DECLARE SUB BISHOP (A, B, XX(), YY(), NDX)
DECLARE SUB MOVELIST (A, B, XX(), YY(), NDX)
DECLARE SUB PAWN (A, B, XX(), YY(), NDX)
DECLARE FUNCTION EVALUATE (ID, PRUNE)

DIM SHARED BOARD(0 TO 7, 0 TO 7)
DIM SHARED BESTA(0 TO 7), BESTB(0 TO 7), BESTX(0 TO 7), BESTY(0 TO 7)
DIM SHARED LEVEL, MAXLEVEL, SCORE, CFLAG
CFLAG = 0
LEVEL = 0
MAXLEVEL = 5

DATA -500,-270,-300,-900,-7500,-300,-270,-500
DATA -100,-100,-100,-100, -100,-100,-100,-100
DATA 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0
DATA 100, 100, 100, 100, 100, 100, 100, 100
DATA 500, 270, 300, 900, 5000, 300, 270, 500
FOR X = 0 TO 7
    FOR Y = 0 TO 7
        READ Z
        BOARD(X, Y) = Z
    NEXT Y
NEXT X

A = -1
RESULT = 0

DO
    SCORE = 0
    CALL IO(A, B, X, Y, RESULT)
    CLS
    CALL SHOWBD
    RESULT = EVALUATE(-1, 10000)
    A = BESTA(1)
    B = BESTB(1)
    X = BESTX(1)
    Y = BESTY(1)
LOOP
end

SUB BISHOP (A, B, XX(), YY(), NDX)
    ID = SGN(BOARD(B, A))
    FOR DXY = 1 TO 7
        X = A - DXY
        Y = B + DXY
        IF X < 0 OR X > 7 OR Y < 0 OR Y > 7 THEN EXIT FOR
        GOSUB 3
        IF BOARD(Y, X) THEN EXIT FOR
    NEXT
    FOR DXY = 1 TO 7
        X = A + DXY
        Y = B + DXY
        IF X < 0 OR X > 7 OR Y < 0 OR Y > 7 THEN EXIT FOR
        GOSUB 3
        IF BOARD(Y, X) THEN EXIT FOR
    NEXT
    FOR DXY = 1 TO 7
        X = A - DXY
        Y = B - DXY
        IF X < 0 OR X > 7 OR Y < 0 OR Y > 7 THEN EXIT FOR
        GOSUB 3
        IF BOARD(Y, X) THEN EXIT FOR
    NEXT
    FOR DXY = 1 TO 7
        X = A + DXY
        Y = B - DXY
        IF X < 0 OR X > 7 OR Y < 0 OR Y > 7 THEN EXIT FOR
        GOSUB 3
        IF BOARD(Y, X) THEN EXIT FOR
    NEXT
    EXIT SUB
3 REM
    IF ID <> SGN(BOARD(Y, X)) THEN
        NDX = NDX + 1
        XX(NDX) = X
        YY(NDX) = Y
    END IF
    RETURN
END SUB

FUNCTION EVALUATE (ID, PRUNE)
    DIM XX(0 TO 26), YY(0 TO 26)
    LEVEL = LEVEL + 1
    BESTSCORE = 10000 * ID
    FOR B = 7 TO 0 STEP -1
        FOR A = 7 TO 0 STEP -1
            IF SGN(BOARD(B, A)) <> ID THEN GOTO 1
            IF (LEVEL = 1) THEN CALL SHOWMAN(A, B, 8)
            CALL MOVELIST(A, B, XX(), YY(), NDX)
            FOR I = 0 TO NDX
                X = XX(I)
                Y = YY(I)
                IF LEVEL = 1 THEN
                    LOCATE 1, 1
                    PRINT "TRYING: "; CHR$(65 + A); 8 - B; "- "; CHR$(65 + X); 8 - Y
                    CALL SHOWMAN(X, Y, 8)
                END IF
                OLDSCORE = SCORE
                MOVER = BOARD(B, A)
                TARGET = BOARD(Y, X)
                CALL MAKEMOVE(A, B, X, Y)
                IF (LEVEL < MAXLEVEL) THEN SCORE = SCORE + EVALUATE(-ID, BESTSCORE - TARGET + ID * (8 - ABS(4 - X) - ABS(4 - Y)))
                SCORE = SCORE + TARGET - ID * (8 - ABS(4 - X) - ABS(4 - Y))
                IF (ID < 0 AND SCORE > BESTSCORE) OR (ID > 0 AND SCORE < BESTSCORE) THEN
                    BESTA(LEVEL) = A
                    BESTB(LEVEL) = B
                    BESTX(LEVEL) = X
                    BESTY(LEVEL) = Y
                    BESTSCORE = SCORE
                    IF (ID < 0 AND BESTSCORE >= PRUNE) OR (ID > 0 AND BESTSCORE <= PRUNE) THEN
                        BOARD(B, A) = MOVER
                        BOARD(Y, X) = TARGET
                        SCORE = OLDSCORE
                        IF (LEVEL = 1) THEN CALL SHOWMAN(X, Y, 0)
                        IF (LEVEL = 1) THEN CALL SHOWMAN(A, B, 0)
                        LEVEL = LEVEL - 1
                        EVALUATE = BESTSCORE
                        EXIT FUNCTION
                    END IF
                END IF
                BOARD(B, A) = MOVER
                BOARD(Y, X) = TARGET
                SCORE = OLDSCORE
                IF (LEVEL = 1) THEN CALL SHOWMAN(X, Y, 0)
            NEXT
1
            IF (LEVEL = 1) THEN CALL SHOWMAN(A, B, 0)
        NEXT
    NEXT
    LEVEL = LEVEL - 1
    EVALUATE = BESTSCORE
END FUNCTION

FUNCTION INCHECK (X)
    DIM XX(27), YY(27), NDX
    FOR B = 0 TO 7
        FOR A = 0 TO 7
            IF BOARD(B, A) >= 0 THEN GOTO 6
            CALL MOVELIST(A, B, XX(), YY(), NDX)
            FOR I = 0 TO NDX STEP 1
                X = XX(I)
                Y = YY(I)
                IF BOARD(Y, X) = 5000 THEN
                    PRINT "YOU ARE IN CHECK!"
                    PRINT " "
                    PRINT " "
                    INCHECK = 1
                    EXIT FUNCTION
                END IF
            NEXT
6             '
        NEXT
    NEXT
    INCHECK = 0
END FUNCTION

SUB IO (A, B, X, Y, RESULT)
    DIM XX(0 TO 26), YY(0 TO 26)
    CLS
    IF A >= 0 THEN
        IF RESULT < -2500 THEN
            PRINT "I RESIGN"
            SLEEP
            SYSTEM
        END IF
        PIECE = BOARD(Y, X)
        CALL MAKEMOVE(A, B, X, Y)
        PRINT "MY MOVE: "; CHR$(65 + A); 8 - B; "- "; CHR$(65 + X); 8 - Y
        IF PIECE THEN
            PRINT "I TOOK YOUR ";
            IF PIECE = 100 THEN PRINT "PAWN"
            IF PIECE = 270 THEN PRINT "KNIGHT"
            IF PIECE = 300 THEN PRINT "BISHOP"
            IF PIECE = 500 THEN PRINT "ROOK"
            IF PIECE = 900 THEN PRINT "QUEEN"
            IF PIECE = 5000 THEN PRINT "KING"
        END IF
        NULL = INCHECK(0)
    END IF
    DO
        CALL SHOWBD
        LOCATE 24, 1
        INPUT "YOUR MOVE: ", IN$
        IF UCASE$(IN$) = "QUIT" THEN CLS : END
        IF UCASE$(IN$) = "O-O" OR IN$ = "0-0" THEN
            IF CFLAG THEN GOTO 16
            IF BOARD(7, 7) <> 500 THEN GOTO 16
            IF BOARD(7, 6) OR BOARD(7, 5) THEN GOTO 16
            BOARD(7, 6) = 5000
            BOARD(7, 4) = 0
            BOARD(7, 5) = 500
            BOARD(7, 7) = 0
            CFLAG = 1
            EXIT SUB
        END IF
        IF UCASE$(IN$) = "O-O-O" OR IN$ = "0-0-0" THEN
            IF CFLAG THEN GOTO 16
            IF BOARD(7, 0) <> 500 THEN GOTO 16
            IF BOARD(7, 1) OR BOARD(7, 2) OR BOARD(7, 3) THEN GOTO 16
            BOARD(7, 2) = 5000
            BOARD(7, 4) = 0
            BOARD(7, 3) = 500
            BOARD(7, 0) = 0
            CFLAG = 1
            EXIT SUB
        END IF
        IF LEN(IN$) < 5 THEN GOTO 16
        B = 8 - (ASC(MID$(IN$, 2, 1)) - 48)
        A = ASC(UCASE$(MID$(IN$, 1, 1))) - 65
        X = ASC(UCASE$(MID$(IN$, 4, 1))) - 65
        Y = 8 - (ASC(MID$(IN$, 5, 1)) - 48)
        IF B > 7 OR B < 0 OR A > 7 OR A < 0 OR X > 7 OR X < 0 OR Y > 7 OR Y < 0 THEN GOTO 16
        IF BOARD(B, A) <= 0 THEN GOTO 16
        CALL MOVELIST(A, B, XX(), YY(), NDX)
        FOR K = 0 TO NDX STEP 1
            IF X = XX(K) AND Y = YY(K) THEN
                MOVER = BOARD(B, A)
                TARGET = BOARD(Y, X)
                CALL MAKEMOVE(A, B, X, Y)
                LOCATE 1, 1
                IF INCHECK(0) = 0 THEN EXIT SUB
                BOARD(B, A) = MOVER
                BOARD(Y, X) = TARGET
                GOTO 16
            END IF
        NEXT
16 '
CLS
    LOOP
END SUB

SUB KING (A, B, XX(), YY(), NDX)
    ID = SGN(BOARD(B, A))
    FOR DY = -1 TO 1
        IF B + DY < 0 OR B + DY > 7 THEN GOTO 12
        FOR DX = -1 TO 1
            IF A + DX < 0 OR A + DX > 7 THEN GOTO 11
            IF ID <> SGN(BOARD(B + DY, A + DX)) THEN
                NDX = NDX + 1
                XX(NDX) = A + DX
                YY(NDX) = B + DY
            END IF
11             '
        NEXT
12         '
    NEXT
END SUB

SUB KNIGHT (A, B, XX(), YY(), NDX)
    ID = SGN(BOARD(B, A))
    X = A - 1
    Y = B - 2
    GOSUB 5
    X = A - 2
    Y = B - 1
    GOSUB 5
    X = A + 1
    Y = B - 2
    GOSUB 5
    X = A + 2
    Y = B - 1
    GOSUB 5
    X = A - 1
    Y = B + 2
    GOSUB 5
    X = A - 2
    Y = B + 1
    GOSUB 5
    X = A + 1
    Y = B + 2
    GOSUB 5
    X = A + 2
    Y = B + 1
    GOSUB 5
    EXIT SUB
5     '
    IF X < 0 OR X > 7 OR Y < 0 OR Y > 7 THEN RETURN
    IF ID <> SGN(BOARD(Y, X)) THEN NDX = NDX + 1: XX(NDX) = X: YY(NDX) = Y
    RETURN
END SUB

SUB MAKEMOVE (A, B, X, Y)
    BOARD(Y, X) = BOARD(B, A)
    BOARD(B, A) = 0
    IF Y = 0 AND BOARD(Y, X) = 100 THEN BOARD(Y, X) = 900
    IF Y = 7 AND BOARD(Y, X) = -100 THEN BOARD(Y, X) = -900
END SUB

SUB MOVELIST (A, B, XX(), YY(), NDX)
    PIECE = INT(ABS(BOARD(B, A)))
    NDX = -1
    IF PIECE = 100 THEN
        CALL PAWN(A, B, XX(), YY(), NDX)
        ELSEIF PIECE = 270 THEN CALL KNIGHT(A, B, XX(), YY(), NDX)
        ELSEIF PIECE = 300 THEN CALL BISHOP(A, B, XX(), YY(), NDX)
        ELSEIF PIECE = 500 THEN CALL ROOK(A, B, XX(), YY(), NDX)
        ELSEIF PIECE = 900 THEN CALL QUEEN(A, B, XX(), YY(), NDX)
        ELSE CALL KING(A, B, XX(), YY(), NDX)
    END IF
END SUB

SUB PAWN (A, B, XX(), YY(), NDX)
    ID = SGN(BOARD(B, A))
    IF (A - 1) >= 0 AND (A - 1) <= 7 AND (B - ID) >= 0 AND (B - ID) <= 7 THEN
        IF SGN(BOARD((B - ID), (A - 1))) = -ID THEN
            NDX = NDX + 1
            XX(NDX) = A - 1
            YY(NDX) = B - ID
        END IF
    END IF
    IF (A + 1) >= 0 AND (A + 1) <= 7 AND (B - ID) >= 0 AND (B - ID) <= 7 THEN
        IF SGN(BOARD((B - ID), (A + 1))) = -ID THEN
            NDX = NDX + 1
            XX(NDX) = A + 1
            YY(NDX) = B - ID
        END IF
    END IF
    IF A >= 0 AND A <= 7 AND (B - ID) >= 0 AND (B - ID) <= 7 THEN
        IF BOARD((B - ID), A) = 0 THEN
            NDX = NDX + 1
            XX(NDX) = A
            YY(NDX) = B - ID
            IF (ID < 0 AND B = 1) OR (ID > 0 AND B = 6) THEN
                IF BOARD((B - ID - ID), A) = 0 THEN
                    NDX = NDX + 1
                    XX(NDX) = A
                    YY(NDX) = B - 2 * ID
                END IF
            END IF
        END IF
    END IF
END SUB

SUB QUEEN (A, B, XX(), YY(), NDX)
    CALL BISHOP(A, B, XX(), YY(), NDX)
    CALL ROOK(A, B, XX(), YY(), NDX)
END SUB

SUB ROOK (A, B, XX(), YY(), NDX)
    ID = SGN(BOARD(B, A))
    FOR X = A - 1 TO 0 STEP -1
        IF ID <> SGN(BOARD(B, X)) THEN
            NDX = NDX + 1
            XX(NDX) = X
            YY(NDX) = B
        END IF
        IF (BOARD(B, X)) THEN EXIT FOR
    NEXT
    FOR X = A + 1 TO 7 STEP 1
        IF ID <> SGN(BOARD(B, X)) THEN
            NDX = NDX + 1
            XX(NDX) = X
            YY(NDX) = B
        END IF
        IF (BOARD(B, X)) THEN EXIT FOR
    NEXT
    FOR Y = B - 1 TO 0 STEP -1
        IF ID <> SGN(BOARD(Y, A)) THEN
            NDX = NDX + 1
            XX(NDX) = A
            YY(NDX) = Y
        END IF
        IF (BOARD(Y, A)) THEN EXIT FOR
    NEXT
    FOR Y = B + 1 TO 7 STEP 1
        IF ID <> SGN(BOARD(Y, A)) THEN
            NDX = NDX + 1
            XX(NDX) = A
            YY(NDX) = Y
        END IF
        IF (BOARD(Y, A)) THEN EXIT FOR
    NEXT
END SUB

SUB SHOWBD
    LOCATE 3, 30
    COLOR 7, 0
    PRINT "A  B  C  D  E  F  G  H"
    FOR K = 0 TO 25
        LOCATE 4, 28 + K
        COLOR 6, 0
        PRINT CHR$(220)
    NEXT
    FOR B = 0 TO 7
        LOCATE 2 * B + 5, 26
        COLOR 7, 0
        PRINT CHR$(56 - B)
        LOCATE 2 * B + 5, 28
        COLOR 6, 0
        PRINT CHR$(219)
        LOCATE 2 * B + 6, 28
        COLOR 6, 0
        PRINT CHR$(219)
        FOR A = 0 TO 7
            IF ((A + B) MOD 2) THEN
                COLOUR = 8
                ELSE COLOUR = 12
            END IF
            CALL SQUARE(3 * A + 31, 2 * B + 5, COLOUR)
        NEXT
        LOCATE 2 * B + 5, 53
        COLOR 6, 0
        PRINT CHR$(219)
        LOCATE 2 * B + 6, 53
        COLOR 6, 0
        PRINT CHR$(219)
        LOCATE 2 * B + 6, 55
        COLOR 7, 0
        PRINT CHR$(56 - B)
    NEXT
    FOR K = 0 TO 25
        LOCATE 21, 28 + K
        COLOR 6, 0
        PRINT CHR$(223)
    NEXT
    LOCATE 22, 30
    COLOR 7, 0
    PRINT "A  B  C  D  E  F  G  H"
    FOR B = 0 TO 7
        FOR A = 0 TO 7
            CALL SHOWMAN(A, B, 0)
        NEXT
    NEXT
    COLOR 7, 0
END SUB

SUB SHOWMAN (A, B, FLAG)
    IF BOARD(B, A) < 0 THEN BACK = 0
    IF BOARD(B, A) > 0 THEN BACK = 7
    FORE = 7 - BACK + FLAG
    IF BOARD(B, A) = 0 THEN
        IF (A + B) AND 1 THEN BACK = 8 ELSE BACK = 12
        FORE = BACK + -1 * (FLAG > 0)
    END IF
    N$ = " "
    PIECE = INT(ABS(BOARD(B, A)))
    IF PIECE = 0 THEN N$ = CHR$(219)
    IF PIECE = 100 THEN N$ = "P"
    IF PIECE = 270 THEN N$ = "N"
    IF PIECE = 300 THEN N$ = "B"
    IF PIECE = 500 THEN N$ = "R"
    IF PIECE = 900 THEN N$ = "Q"
    IF PIECE = 5000 OR PIECE = 7500 THEN N$ = "K"
    LOCATE 2 * B + 5 - (BOARD(B, A) > 0), 3 * A + 30
    COLOR FORE, BACK
    PRINT N$
    LOCATE 1, 1
    COLOR 7, 0
END SUB

SUB SQUARE (A, B, C)
    MT$ = CHR$(219)
    MT$ = MT$ + MT$ + MT$
    LOCATE B, A - 2
    COLOR C, C
    PRINT MT$
    LOCATE B + 1, A - 2
    COLOR C, C
    PRINT MT$
    COLOR 7, 0
END SUB

This is an open source chess program written in FreeBASIC that I found on Github FreeBasicChess.

There are a number of open source Chess programs written in Go on Github.

Rather than spend a lot of time trying to write my own (likely mediocre) program, I thought I'd simply post a link to notnil/chess which explains its various capabilities quite well. However, you need to look at the code itself to see that it can cope with all types of move including castling, en passant capture and promotion to a piece of the player's choice.


Works with: Java version LTS version 17
Translation of: Julia
import java.util.*;



// Main class for testing
class ChessEngine {
    public static void testChess() {
      
        // Position with check
        String fenCheck = "4k3/8/8/7Q/8/8/8/4K3 b - - 0 1";
        Board boardCheck = Board.parseFEN(fenCheck);
        System.out.println("--- Position with Black in Check ---");
        boardCheck.print();
        System.out.println("\nIs Black king in check? " + boardCheck.inCheck('b'));
        List<Move> movesCheck = boardCheck.getLegalMoves();
        System.out.println("\nLegal moves for Black (to get out of check or make other moves):");
        
        
        
        // Starting position
        String fenStart = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
        Board boardStart = Board.parseFEN(fenStart);
        System.out.println("--- Starting Position ---");
        boardStart.print();
        List<Move> movesStart = boardStart.getLegalMoves();
        System.out.println("\nLegal moves for Black (to get out of check or make other moves):");
        if (movesCheck.isEmpty()) {
            System.out.println("No legal moves. (This might indicate checkmate or stalemate)");
        } else {
            for (Move move : movesCheck) {
                System.out.println("- " + move);
            }
        }
        System.out.println("Total legal moves: " + movesCheck.size());
        System.out.println("\n" + "=".repeat(40) + "\n");
        
        // Mid-game position
        String fenMidgame = "r3k2r/p1ppqpb1/bn2pn1p/3PP1p1/1p2P3/2N2N2/PPPBBPPP/R2QK2R w KQkq - 0 1";
        Board boardMidgame = Board.parseFEN(fenMidgame);
        System.out.println("--- Mid-game Position (White to move) ---");
        boardMidgame.print();
        List<Move> movesMidgame = boardMidgame.getLegalMoves();
        System.out.println("\nLegal moves for White:");
        if (movesMidgame.isEmpty()) {
            System.out.println("No legal moves.");
        } else {
            for (Move move : movesMidgame) {
                System.out.println("- " + move);
            }
        }
        System.out.println("Total legal moves: " + movesMidgame.size());
        System.out.println("\n" + "=".repeat(40) + "\n");
        
        // Castling position
        String fenCastlingWhite = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1";
        Board boardCastlingWhite = Board.parseFEN(fenCastlingWhite);
        System.out.println("--- Position Allowing White Castling ---");
        boardCastlingWhite.print();
        List<Move> movesCastlingWhite = boardCastlingWhite.getLegalMoves();
        System.out.println("\nLegal moves for White (including castling):");
        if (movesCastlingWhite.isEmpty()) {
            System.out.println("No legal moves.");
        } else {
            for (Move move : movesCastlingWhite) {
                System.out.println("- " + move);
            }
        }
        System.out.println("Total legal moves: " + movesCastlingWhite.size());
        System.out.println("\n" + "=".repeat(40) + "\n");
        
        // Pawn promotion position
        String fenPromotion = "8/4P2k/8/8/8/8/8/R3K2R w KQkq - 0 1";
        Board boardPromotion = Board.parseFEN(fenPromotion);
        System.out.println("--- Position Allowing Pawn Promotion ---");
        boardPromotion.print();
        List<Move> movesPromotion = boardPromotion.getLegalMoves();
        System.out.println("\nLegal moves for White (including pawn promotions):");
        if (movesPromotion.isEmpty()) {
            System.out.println("No legal moves.");
        } else {
            for (Move move : movesPromotion) {
                System.out.println("- " + move);
            }
        }
        System.out.println("Total legal moves: " + movesPromotion.size());
        System.out.println("\n" + "=".repeat(40) + "\n");
        
        // En passant position
        String fenEnpassant = "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3";
        Board boardEnpassant = Board.parseFEN(fenEnpassant);
        System.out.println("--- Position Allowing En Passant ---");
        boardEnpassant.print();
        List<Move> movesEnpassant = boardEnpassant.getLegalMoves();
        System.out.println("\nLegal moves for White (including en passant capture):");
        if (movesEnpassant.isEmpty()) {
            System.out.println("No legal moves.");
        } else {
            for (Move move : movesEnpassant) {
                System.out.println("- " + move);
            }
        }
        System.out.println("Total legal moves: " + movesEnpassant.size());
        System.out.println("\n" + "=".repeat(40) + "\n");
        
        // Stalemate position
        String fenStalemate = "8/8/8/8/8/6k1/5b2/7K w - - 12 95";
        Board boardStalemate = Board.parseFEN(fenStalemate);
        System.out.println("--- Stalemate Position ---");
        boardStalemate.print();
        List<Move> movesStalemate = boardStalemate.getLegalMoves();
        System.out.println("\nLegal moves for White (should be none):");
        if (movesStalemate.isEmpty()) {
            System.out.println("No legal moves. (This indicates stalemate)");
        } else {
            for (Move move : movesStalemate) {
                System.out.println("- " + move);
            }
        }
        System.out.println("Total legal moves: " + movesStalemate.size());
        System.out.println("\n" + "=".repeat(40) + "\n");
    }
    
    public static void main(String[] args) {
        testChess();
    }
    
}


// Chess Piece class
class Piece {
    private char type; // 'P', 'R', 'N', 'B', 'Q', 'K'
    private char color; // 'w', 'b'
    
    public Piece(char type, char color) {
        this.type = type;
        this.color = color;
    }
    
    public char getType() { return type; }
    public char getColor() { return color; }
    
    @Override
    public String toString() {
        return color == 'w' ? String.valueOf(type) : String.valueOf(Character.toLowerCase(type));
    }
}

// Chess Move class
class Move {
    private int startRow, startCol, endRow, endCol;
    private Piece promotionPiece; // for pawn promotion
    
    public Move(int startRow, int startCol, int endRow, int endCol) {
        this(startRow, startCol, endRow, endCol, null);
    }
    
    public Move(int startRow, int startCol, int endRow, int endCol, Piece promotionPiece) {
        this.startRow = startRow;
        this.startCol = startCol;
        this.endRow = endRow;
        this.endCol = endCol;
        this.promotionPiece = promotionPiece;
    }
    
    public int getStartRow() { return startRow; }
    public int getStartCol() { return startCol; }
    public int getEndRow() { return endRow; }
    public int getEndCol() { return endCol; }
    public Piece getPromotionPiece() { return promotionPiece; }
    
    // Convert (row, col) 1-based position to algebraic notation
    public static String algebraic(int row, int col) {
        char file = (char)('a' + (col - 1));
        char rank = (char)('8' - (row - 1));
        return "" + file + rank;
    }
    
    @Override
    public String toString() {
        String result = algebraic(startRow, startCol) + algebraic(endRow, endCol);
        if (promotionPiece != null && promotionPiece.getType() == 'P' && (endRow == 1 || endRow == 8)) {
            result += "=" + promotionPiece.getType();
        }
        return result;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Move move = (Move) obj;
        return startRow == move.startRow && startCol == move.startCol && 
               endRow == move.endRow && endCol == move.endCol &&
               Objects.equals(promotionPiece, move.promotionPiece);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(startRow, startCol, endRow, endCol, promotionPiece);
    }
}

// Chess Board class
class Board {
    private Piece[][] squares;
    private char turn; // 'w' or 'b'
    private String castlingRights; // "KQkq"
    private int[] enPassant; // [row, col] or [0, 0] if none
    private int halfMoveClock;
    private int fullMoveClock;
    
    public Board(Piece[][] squares, char turn, String castlingRights, int[] enPassant, int halfMoveClock, int fullMoveClock) {
        this.squares = new Piece[8][8];
        for (int i = 0; i < 8; i++) {
            System.arraycopy(squares[i], 0, this.squares[i], 0, 8);
        }
        this.turn = turn;
        this.castlingRights = castlingRights;
        this.enPassant = Arrays.copyOf(enPassant, 2);
        this.halfMoveClock = halfMoveClock;
        this.fullMoveClock = fullMoveClock;
    }
    
    public Piece getPieceAt(int row, int col) {
        return squares[row - 1][col - 1]; // Convert to 0-based indexing
    }
    
    public char getTurn() { return turn; }
    public String getCastlingRights() { return castlingRights; }
    public int[] getEnPassant() { return enPassant; }
    
    // Convert algebraic notation to (row, col) tuple
    public static int[] algebraicToTuple(String alg) {
        if (alg.length() != 2 || alg.equals("-")) return new int[]{0, 0};
        char file = alg.charAt(0);
        char rank = alg.charAt(1);
        int col = file - 'a' + 1;
        int row = '8' - rank + 1;
        return new int[]{row, col};
    }
    
    // Parse FEN string
    public static Board parseFEN(String fen) {
        String[] parts = fen.split(" ");
        String boardStr = parts[0];
        char turnChar = parts[1].charAt(0);
        String castlingRights = parts[2];
        int[] enPassant = algebraicToTuple(parts[3]);
        int halfMove = Integer.parseInt(parts[4]);
        int fullMove = Integer.parseInt(parts[5]);
        
        Piece[][] squares = new Piece[8][8];
        int row = 0, col = 0;
        
        for (char c : boardStr.toCharArray()) {
            if (c == '/') {
                row++;
                col = 0;
            } else if (Character.isDigit(c)) {
                int numEmpty = Character.getNumericValue(c);
                for (int i = 0; i < numEmpty; i++) {
                    squares[row][col] = null;
                    col++;
                }
            } else {
                char color = Character.isUpperCase(c) ? 'w' : 'b';
                char pieceType = Character.toUpperCase(c);
                squares[row][col] = new Piece(pieceType, color);
                col++;
            }
        }
        
        return new Board(squares, turnChar, castlingRights, enPassant, halfMove, fullMove);
    }
    
    // Print board
    public void print() {
        System.out.println("  a b c d e f g h");
        System.out.println(" +-----------------+");
        for (int r = 0; r < 8; r++) {
            System.out.print((8 - r) + "|");
            for (int c = 0; c < 8; c++) {
                Piece piece = squares[r][c];
                if (piece == null) {
                    System.out.print(" .");
                } else {
                    System.out.print(" " + piece);
                }
            }
            System.out.println(" |" + (8 - r));
        }
        System.out.println(" +-----------------+");
        System.out.println("  a b c d e f g h");
        System.out.println("Current turn: " + (turn == 'w' ? "White" : "Black"));
        System.out.println("Castling rights: " + castlingRights);
    }
    
    // Check if square is valid
    public static boolean isValidSquare(int row, int col) {
        return row >= 1 && row <= 8 && col >= 1 && col <= 8;
    }
    
    // Apply move and return new board
    public Board makeMove(Move move) {
        Piece[][] newSquares = new Piece[8][8];
        for (int i = 0; i < 8; i++) {
            System.arraycopy(squares[i], 0, newSquares[i], 0, 8);
        }
        
        Piece movingPiece = getPieceAt(move.getStartRow(), move.getStartCol());
        if (movingPiece == null) return this;
        
        // Handle castling
        if (movingPiece.getType() == 'K' && Math.abs(move.getStartCol() - move.getEndCol()) == 2) {
            int kingRow = move.getStartRow();
            if (move.getEndCol() == 7) { // kingside castling
                newSquares[kingRow - 1][6] = movingPiece;
                newSquares[kingRow - 1][move.getStartCol() - 1] = null;
                Piece rook = getPieceAt(kingRow, 8);
                newSquares[kingRow - 1][5] = rook;
                newSquares[kingRow - 1][7] = null;
            } else if (move.getEndCol() == 3) { // queenside castling
                newSquares[kingRow - 1][2] = movingPiece;
                newSquares[kingRow - 1][move.getStartCol() - 1] = null;
                Piece rook = getPieceAt(kingRow, 1);
                newSquares[kingRow - 1][3] = rook;
                newSquares[kingRow - 1][0] = null;
            }
        } else if (movingPiece.getType() == 'P') {
            // Handle en passant
            if (move.getEndCol() != move.getStartCol() && getPieceAt(move.getEndRow(), move.getEndCol()) == null) {
                int targetRow = move.getEndRow() + (movingPiece.getColor() == 'w' ? 1 : -1);
                Piece capturedPiece = getPieceAt(targetRow, move.getEndCol());
                if (capturedPiece != null && capturedPiece.getType() == 'P' && 
                    capturedPiece.getColor() != movingPiece.getColor()) {
                    newSquares[targetRow - 1][move.getEndCol() - 1] = null;
                    newSquares[move.getEndRow() - 1][move.getEndCol() - 1] = movingPiece;
                    newSquares[move.getStartRow() - 1][move.getStartCol() - 1] = null;
                }
            } else {
                // Regular pawn move
                newSquares[move.getEndRow() - 1][move.getEndCol() - 1] = movingPiece;
                newSquares[move.getStartRow() - 1][move.getStartCol() - 1] = null;
            }
        } else {
            // Handle all other moves
            newSquares[move.getEndRow() - 1][move.getEndCol() - 1] = movingPiece;
            newSquares[move.getStartRow() - 1][move.getStartCol() - 1] = null;
        }
        
        // Update castling rights
        String newCastlingRights = castlingRights;
        if (movingPiece.getType() == 'K') {
            if (movingPiece.getColor() == 'w') {
                newCastlingRights = newCastlingRights.replace("K", "").replace("Q", "");
            } else {
                newCastlingRights = newCastlingRights.replace("k", "").replace("q", "");
            }
        } else if (movingPiece.getType() == 'R') {
            if (movingPiece.getColor() == 'w') {
                if (move.getStartRow() == 8 && move.getStartCol() == 8) {
                    newCastlingRights = newCastlingRights.replace("K", "");
                } else if (move.getStartRow() == 8 && move.getStartCol() == 1) {
                    newCastlingRights = newCastlingRights.replace("Q", "");
                }
            } else {
                if (move.getStartRow() == 1 && move.getStartCol() == 8) {
                    newCastlingRights = newCastlingRights.replace("k", "");
                } else if (move.getStartRow() == 1 && move.getStartCol() == 1) {
                    newCastlingRights = newCastlingRights.replace("q", "");
                }
            }
        }
        
        if (newCastlingRights.isEmpty()) {
            newCastlingRights = "-";
        }
        
        // Handle en passant
        int[] newEnPassant = {0, 0};
        if (movingPiece.getType() == 'P' && Math.abs(move.getStartRow() - move.getEndRow()) == 2) {
            newEnPassant[0] = move.getEndRow() + (movingPiece.getColor() == 'w' ? 1 : -1);
            newEnPassant[1] = move.getEndCol();
        }
        
        char newTurn = (turn == 'w' ? 'b' : 'w');
        return new Board(newSquares, newTurn, newCastlingRights, newEnPassant,
                        halfMoveClock + 1, fullMoveClock + (newTurn == 'b' ? 1 : 0));
    }
    
    // Find king position
    public int[] findKing(char kingColor) {
        for (int r = 1; r <= 8; r++) {
            for (int c = 1; c <= 8; c++) {
                Piece piece = getPieceAt(r, c);
                if (piece != null && piece.getType() == 'K' && piece.getColor() == kingColor) {
                    return new int[]{r, c};
                }
            }
        }
        return null;
    }
    
    // Check if square is attacked
    public boolean isSquareAttacked(int row, int col, char attackingColor) {
        // Check pawn attacks
        int pawnDir = (attackingColor == 'w' ? -1 : 1);
        for (int dc : new int[]{-1, 1}) {
            int targetRow = row + pawnDir;
            int targetCol = col + dc;
            if (isValidSquare(targetRow, targetCol)) {
                Piece piece = getPieceAt(targetRow, targetCol);
                if (piece != null && piece.getColor() == attackingColor && piece.getType() == 'P') {
                    return true;
                }
            }
        }
        
        // Check knight attacks
        int[][] knightMoves = {{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}};
        for (int[] move : knightMoves) {
            int targetRow = row + move[0];
            int targetCol = col + move[1];
            if (isValidSquare(targetRow, targetCol)) {
                Piece piece = getPieceAt(targetRow, targetCol);
                if (piece != null && piece.getColor() == attackingColor && piece.getType() == 'N') {
                    return true;
                }
            }
        }
        
        // Check straight line attacks (rook, queen)
        int[][] straightDirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        for (int[] dir : straightDirs) {
            for (int i = 1; i <= 7; i++) {
                int targetRow = row + i * dir[0];
                int targetCol = col + i * dir[1];
                if (!isValidSquare(targetRow, targetCol)) break;
                
                Piece piece = getPieceAt(targetRow, targetCol);
                if (piece != null) {
                    if (piece.getColor() == attackingColor && 
                        (piece.getType() == 'R' || piece.getType() == 'Q')) {
                        return true;
                    } else {
                        break;
                    }
                }
            }
        }
        
        // Check diagonal attacks (bishop, queen)
        int[][] diagonalDirs = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
        for (int[] dir : diagonalDirs) {
            for (int i = 1; i <= 7; i++) {
                int targetRow = row + i * dir[0];
                int targetCol = col + i * dir[1];
                if (!isValidSquare(targetRow, targetCol)) break;
                
                Piece piece = getPieceAt(targetRow, targetCol);
                if (piece != null) {
                    if (piece.getColor() == attackingColor && 
                        (piece.getType() == 'B' || piece.getType() == 'Q')) {
                        return true;
                    } else {
                        break;
                    }
                }
            }
        }
        
        // Check king attacks
        int[][] kingMoves = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}};
        for (int[] move : kingMoves) {
            int targetRow = row + move[0];
            int targetCol = col + move[1];
            if (isValidSquare(targetRow, targetCol)) {
                Piece piece = getPieceAt(targetRow, targetCol);
                if (piece != null && piece.getColor() == attackingColor && piece.getType() == 'K') {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    // Check if king is in check
    public boolean inCheck(char color) {
        int[] kingPos = findKing(color);
        if (kingPos == null) {
            System.out.println("Warning: King of color " + color + " not found on board.");
            return false;
        }
        char attackingColor = (color == 'w' ? 'b' : 'w');
        return isSquareAttacked(kingPos[0], kingPos[1], attackingColor);
    }
    
    // Get legal moves
    public List<Move> getLegalMoves() {
        List<Move> allMoves = new ArrayList<>();
        char movingPlayerColor = turn;
        
        for (int r = 1; r <= 8; r++) {
            for (int c = 1; c <= 8; c++) {
                Piece piece = getPieceAt(r, c);
                if (piece != null && piece.getColor() == movingPlayerColor) {
                    switch (piece.getType()) {
                        case 'P': allMoves.addAll(getPawnMoves(r, c, piece)); break;
                        case 'R': allMoves.addAll(getRookMoves(r, c, piece)); break;
                        case 'N': allMoves.addAll(getKnightMoves(r, c, piece)); break;
                        case 'B': allMoves.addAll(getBishopMoves(r, c, piece)); break;
                        case 'Q': allMoves.addAll(getQueenMoves(r, c, piece)); break;
                        case 'K': allMoves.addAll(getKingMoves(r, c, piece)); break;
                    }
                }
            }
        }
        
        // Filter out moves that leave king in check
        List<Move> legalMoves = new ArrayList<>();
        for (Move move : allMoves) {
            Board tempBoard = makeMove(move);
            if (!tempBoard.inCheck(movingPlayerColor)) {
                legalMoves.add(move);
            }
        }
        
        return legalMoves;
    }
    
    // Get pawn moves
    private List<Move> getPawnMoves(int r, int c, Piece piece) {
        List<Move> moves = new ArrayList<>();
        int direction = (piece.getColor() == 'w' ? -1 : 1);
        int startRank = (piece.getColor() == 'w' ? 7 : 2);
        
        // Forward move
        int targetRow = r + direction;
        if (isValidSquare(targetRow, c) && getPieceAt(targetRow, c) == null) {
            if (targetRow == 1 || targetRow == 8) { // Promotion
                for (char promotionType : new char[]{'Q', 'R', 'B', 'N'}) {
                    moves.add(new Move(r, c, targetRow, c, new Piece(promotionType, piece.getColor())));
                }
            } else {
                moves.add(new Move(r, c, targetRow, c));
                // Double move from starting position
                if (r == startRank) {
                    int doubleTargetRow = r + 2 * direction;
                    if (isValidSquare(doubleTargetRow, c) && getPieceAt(doubleTargetRow, c) == null) {
                        moves.add(new Move(r, c, doubleTargetRow, c));
                    }
                }
            }
        }
        
        // Capture moves
        for (int dc : new int[]{-1, 1}) {
            targetRow = r + direction;
            int targetCol = c + dc;
            if (isValidSquare(targetRow, targetCol)) {
                Piece targetPiece = getPieceAt(targetRow, targetCol);
                if ((targetPiece != null && targetPiece.getColor() != piece.getColor()) ||
                    (enPassant[0] == targetRow && enPassant[1] == targetCol)) {
                    moves.add(new Move(r, c, targetRow, targetCol));
                }
            }
        }
        
        return moves;
    }
    
    // Get sliding moves (for rook, bishop, queen)
    private List<Move> getSlidingMoves(int r, int c, Piece piece, int[][] directions) {
        List<Move> moves = new ArrayList<>();
        for (int[] dir : directions) {
            for (int i = 1; i <= 7; i++) {
                int targetRow = r + i * dir[0];
                int targetCol = c + i * dir[1];
                if (!isValidSquare(targetRow, targetCol)) break;
                
                Piece targetPiece = getPieceAt(targetRow, targetCol);
                if (targetPiece == null) {
                    moves.add(new Move(r, c, targetRow, targetCol));
                } else if (targetPiece.getColor() != piece.getColor()) {
                    moves.add(new Move(r, c, targetRow, targetCol));
                    break;
                } else {
                    break;
                }
            }
        }
        return moves;
    }
    
    private List<Move> getRookMoves(int r, int c, Piece piece) {
        int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        return getSlidingMoves(r, c, piece, directions);
    }
    
    private List<Move> getBishopMoves(int r, int c, Piece piece) {
        int[][] directions = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
        return getSlidingMoves(r, c, piece, directions);
    }
    
    private List<Move> getQueenMoves(int r, int c, Piece piece) {
        int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
        return getSlidingMoves(r, c, piece, directions);
    }
    
    private List<Move> getKnightMoves(int r, int c, Piece piece) {
        List<Move> moves = new ArrayList<>();
        int[][] knightOffsets = {{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}};
        for (int[] offset : knightOffsets) {
            int targetRow = r + offset[0];
            int targetCol = c + offset[1];
            if (isValidSquare(targetRow, targetCol)) {
                Piece targetPiece = getPieceAt(targetRow, targetCol);
                if (targetPiece == null || targetPiece.getColor() != piece.getColor()) {
                    moves.add(new Move(r, c, targetRow, targetCol));
                }
            }
        }
        return moves;
    }
    
    private List<Move> getKingMoves(int r, int c, Piece piece) {
        List<Move> moves = new ArrayList<>();
        int[][] kingOffsets = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}};
        for (int[] offset : kingOffsets) {
            int targetRow = r + offset[0];
            int targetCol = c + offset[1];
            if (isValidSquare(targetRow, targetCol)) {
                Piece targetPiece = getPieceAt(targetRow, targetCol);
                if (targetPiece == null || targetPiece.getColor() != piece.getColor()) {
                    moves.add(new Move(r, c, targetRow, targetCol));
                }
            }
        }
        
        // Castling
        char kingColor = piece.getColor();
        char opponentColor = (kingColor == 'w' ? 'b' : 'w');
        if (inCheck(kingColor)) return moves;
        
        int initialKingRow = (kingColor == 'w' ? 8 : 1);
        if (!(r == initialKingRow && c == 5)) return moves;
        
        // Kingside castling
        if (kingColor == 'w' && castlingRights.contains("K")) {
            if (getPieceAt(initialKingRow, 6) == null && getPieceAt(initialKingRow, 7) == null) {
                Piece rook = getPieceAt(initialKingRow, 8);
                if (rook != null && rook.getType() == 'R' && rook.getColor() == kingColor) {
                    if (!isSquareAttacked(initialKingRow, 5, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 6, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 7, opponentColor)) {
                        moves.add(new Move(r, c, initialKingRow, 7));
                    }
                }
            }
        }
        
        // Queenside castling
        if (kingColor == 'w' && castlingRights.contains("Q")) {
            if (getPieceAt(initialKingRow, 2) == null && getPieceAt(initialKingRow, 3) == null &&
                getPieceAt(initialKingRow, 4) == null) {
                Piece rook = getPieceAt(initialKingRow, 1);
                if (rook != null && rook.getType() == 'R' && rook.getColor() == kingColor) {
                    if (!isSquareAttacked(initialKingRow, 5, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 4, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 3, opponentColor)) {
                        moves.add(new Move(r, c, initialKingRow, 3));
                    }
                }
            }
        }
        
        // Black castling
        if (kingColor == 'b' && castlingRights.contains("k")) {
            if (getPieceAt(initialKingRow, 6) == null && getPieceAt(initialKingRow, 7) == null) {
                Piece rook = getPieceAt(initialKingRow, 8);
                if (rook != null && rook.getType() == 'R' && rook.getColor() == kingColor) {
                    if (!isSquareAttacked(initialKingRow, 5, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 6, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 7, opponentColor)) {
                        moves.add(new Move(r, c, initialKingRow, 7));
                    }
                }
            }
        }
        
        if (kingColor == 'b' && castlingRights.contains("q")) {
            if (getPieceAt(initialKingRow, 2) == null && getPieceAt(initialKingRow, 3) == null &&
                getPieceAt(initialKingRow, 4) == null) {
                Piece rook = getPieceAt(initialKingRow, 1);
                if (rook != null && rook.getType() == 'R' && rook.getColor() == kingColor) {
                    if (!isSquareAttacked(initialKingRow, 5, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 4, opponentColor) &&
                        !isSquareAttacked(initialKingRow, 3, opponentColor)) {
                        moves.add(new Move(r, c, initialKingRow, 3));
                    }
                }
            }
        }
        
        return moves;
    }
}
Output:
--- Position with Black in Check ---
  a b c d e f g h
 +-----------------+
8| . . . . k . . . |8
7| . . . . . . . . |7
6| . . . . . . . . |6
5| . . . . . . . Q |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| . . . . . . . . |2
1| . . . . K . . . |1
 +-----------------+
  a b c d e f g h
Current turn: Black
Castling rights: -

Is Black king in check? true

Legal moves for Black (to get out of check or make other moves):
--- Starting Position ---
  a b c d e f g h
 +-----------------+
8| r n b q k b n r |8
7| p p p p p p p p |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| P P P P P P P P |2
1| R N B Q K B N R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for Black (to get out of check or make other moves):
- e8d8
- e8f8
- e8d7
- e8e7
Total legal moves: 4

========================================

--- Mid-game Position (White to move) ---
  a b c d e f g h
 +-----------------+
8| r . . . k . . r |8
7| p . p p q p b . |7
6| b n . . p n . p |6
5| . . . P P . p . |5
4| . p . . P . . . |4
3| . . N . . N . . |3
2| P P P B B P P P |2
1| R . . Q K . . R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White:
- d5d6
- d5e6
- e5f6
- c3b5
- c3a4
- c3b1
- f3g5
- f3d4
- f3h4
- f3g1
- a2a3
- a2a4
- b2b3
- d2c1
- d2e3
- d2f4
- d2g5
- e2f1
- e2d3
- e2c4
- e2b5
- e2a6
- g2g3
- g2g4
- h2h3
- h2h4
- a1b1
- a1c1
- d1c1
- d1b1
- e1f1
- e1g1
- h1g1
- h1f1
Total legal moves: 34

========================================

--- Position Allowing White Castling ---
  a b c d e f g h
 +-----------------+
8| r . . . k . . r |8
7| . . . . . . . . |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| . . . . . . . . |2
1| R . . . K . . R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White (including castling):
- a1b1
- a1c1
- a1d1
- a1a2
- a1a3
- a1a4
- a1a5
- a1a6
- a1a7
- a1a8
- e1d2
- e1e2
- e1f2
- e1d1
- e1f1
- e1g1
- e1c1
- h1g1
- h1f1
- h1h2
- h1h3
- h1h4
- h1h5
- h1h6
- h1h7
- h1h8
Total legal moves: 26

========================================

--- Position Allowing Pawn Promotion ---
  a b c d e f g h
 +-----------------+
8| . . . . . . . . |8
7| . . . . P . . k |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| . . . . . . . . |2
1| R . . . K . . R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White (including pawn promotions):
- e7e8
- e7e8
- e7e8
- e7e8
- a1b1
- a1c1
- a1d1
- a1a2
- a1a3
- a1a4
- a1a5
- a1a6
- a1a7
- a1a8
- e1d2
- e1e2
- e1f2
- e1d1
- e1f1
- e1g1
- e1c1
- h1g1
- h1f1
- h1h2
- h1h3
- h1h4
- h1h5
- h1h6
- h1h7
Total legal moves: 29

========================================

--- Position Allowing En Passant ---
  a b c d e f g h
 +-----------------+
8| r n b q k b n r |8
7| p p p . p . p p |7
6| . . . . . . . . |6
5| . . . p P p . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| P P P P . P P P |2
1| R N B Q K B N R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White (including en passant capture):
- e5e6
- e5f6
- a2a3
- a2a4
- b2b3
- b2b4
- c2c3
- c2c4
- d2d3
- d2d4
- f2f3
- f2f4
- g2g3
- g2g4
- h2h3
- h2h4
- b1a3
- b1c3
- d1e2
- d1f3
- d1g4
- d1h5
- e1e2
- f1e2
- f1d3
- f1c4
- f1b5
- f1a6
- g1f3
- g1h3
- g1e2
Total legal moves: 31

========================================

--- Stalemate Position ---
  a b c d e f g h
 +-----------------+
8| . . . . . . . . |8
7| . . . . . . . . |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . k . |3
2| . . . . . b . . |2
1| . . . . . . . K |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: -

Legal moves for White (should be none):
No legal moves. (This indicates stalemate)
Total legal moves: 0

========================================



A full chess-playing program seems too complex a project for this wiki. The program below does not actually play chess but can take a position given by FEN notation (see Forsyth–Edwards_Notation) and return all legal moves, including en passant, pawn promotion, and castling.

""" Chess moves """

""" Chess Piece struct. type: Char ('P', 'R', 'N', 'B', 'Q', 'K') color: 'w', 'b' """
struct Piece
    type::Char
    color::Char
end
Base.print(io::IO, p::Piece) = print(io, p.color == 'w' ? p.type : lowercase(p.type))

""" Chess Board is 8 x 8 Matrix """
struct Board
    squares::Matrix{Union{Piece, Nothing}}
    turn::Char # 'w' or 'b'
    castlingrights::String # starts as "KQkq", kingside queenside for white, black
    enpassant::Tuple{Int, Int} # position of a pawn that can be captured en passant, (0, 0) if none
    halfmoveclock::Int # for fifty-move rule
    fullmoveclock::Int # running total of full moves
end

""" Chess Move struct, 1-based, defined by the movement not the Piece moved """
struct Move
    start_row::Int
    start_col::Int
    end_row::Int
    end_col::Int
    piece::Union{Piece, Nothing} # for pawn promotion
    function Move(start_row::Int, start_col::Int, end_row::Int, end_col::Int, piece::Union{Piece, Nothing} = nothing)
        new(start_row, start_col, end_row, end_col, piece)
    end
end

""" Convert (row, col) 1-based position to algebraic notation (e.g., "a1") """
function algebraic(row::Int, col::Int, piece::Union{Piece, Nothing} = nothing)::String
    file = Char('a' + (col - 1))  # columns are 1 to 8, map to 'a' to 'h'
    rank = Char('8' - (row - 1))  # rows are 1 to 8, map to '8' to '1'
    # Handle pawn promotion
    if piece !== nothing && piece.type == 'P' && (row == 1 || row == 8)
        return string(file, rank, "=", piece.type)
    end
    return string(file, rank)
end

""" Convert a Move to algebraic notation (e.g., "e2e4") """
function Base.print(io::IO, m::Move)
    print(io, "$(algebraic(m.start_row, m.start_col, m.piece))$(algebraic(m.end_row, m.end_col, m.piece))")
end

""" Convert algebraic notation to a Tuple{Int, Int}. """
function algebraic2tuple(alg::AbstractString)::Tuple{Int, Int}
    length(alg) != 2 && return (0, 0) # "-" or invalid input
    file = alg[1]
    rank = alg[2]
    col = Int(file - 'a') + 1  # 'a' maps to 1, 'h' to 8
    row = Int('8' - rank) + 1  # '8' maps to 1, '1' to 8
    return (row, col)
end

""" Parses a FEN string. Returns a Board set per FEN """
function parseFEN(fen_string::String)::Board
    parts = split(fen_string, ' ')
    boardstr = parts[1]
    turnchar = parts[2][1]
    castlingrights = parts[3]
    enpassant = algebraic2tuple(parts[4])
    halfmove = parse(Int, parts[5])
    fullmove = parse(Int, parts[6])

    squares = Matrix{Union{Piece, Nothing}}(undef, 8, 8)
    row, col = 1, 1
    for char in boardstr
        if char == '/'
            row += 1
            col = 1
        elseif isdigit(char)
            num_empty = parse(Int, char)
            for _ in 1:num_empty
                squares[row, col] = nothing
                col += 1
            end
        else
            color = isuppercase(char) ? 'w' : 'b'
            piece_type = uppercase(char)
            squares[row, col] = Piece(piece_type, color)
            col += 1
        end
    end
    return Board(squares, turnchar, castlingrights, enpassant, halfmove, fullmove)
end

""" Prints an ASCII visual representation of the chess board to stdout """
function Base.print(io::IO, board::Board)
    println(io, "  a b c d e f g h")
    println(io, " +-----------------+")
    for r in 1:8
        print(io, "$(9 - r)|")
        for c in 1:8
            piece = board.squares[r, c]
            if piece === nothing
                print(io, " .")
            else
                print(io, " $piece")
            end
        end
        println(io, " |$(9 - r)")
    end
    println(io, " +-----------------+")
    println(io, "  a b c d e f g h")
    println(io, "Current turn: $(board.turn == 'w' ? "White" : "Black")")
    println(io, "Castling rights: $(board.castlingrights)")
end

""" Checks if the given row and column are within the 8x8 board boundaries (1-indexed). """
@inline isvalidsquare(row::Int, col::Int)::Bool = 1 <= row <= 8 && 1 <= col <= 8

""" Returns the piece at the 1-based (row, col) on the board, or nothing if empty. """
piece_at(brd::Board, row, col)::Union{Piece, Nothing} = brd.squares[row, col]

""" Applies a move to a new (cloned) board state and returns the new Board. """
function move!(board::Board, move::Move)::Board
    new_squares = deepcopy(board.squares)
    moving_piece = piece_at(board, move.start_row, move.start_col)
    moving_piece === nothing && return board # sanity check

    # Handle castling
    if moving_piece.type == 'K' && abs(move.start_col - move.end_col) == 2
        king_row = move.start_row
        if move.end_col == 7 # kingside castling
            new_squares[king_row, 7] = moving_piece
            new_squares[king_row, move.start_col] = nothing
            rook = piece_at(board, king_row, 8)
            new_squares[king_row, 6] = rook
            new_squares[king_row, 8] = nothing
        elseif move.end_col == 3 # queenside castling
            new_squares[king_row, 3] = moving_piece
            new_squares[king_row, move.start_col] = nothing
            rook = piece_at(board, king_row, 1)
            new_squares[king_row, 4] = rook
            new_squares[king_row, 1] = nothing
        end
    elseif moving_piece.type == 'P' # handle special en passant pawn moves
        if move.end_col != move.start_col && piece_at(board, move.end_row, move.end_col) isa Nothing
            target_row = move.end_row + (moving_piece.color == 'w' ? 1 : -1)
            captured_piece = piece_at(board, target_row, move.end_col)
            if captured_piece !== nothing && captured_piece.type == 'P' && captured_piece.color != moving_piece.color
                new_squares[target_row, move.end_col] = nothing
                new_squares[move.end_row, move.end_col] = moving_piece
                new_squares[move.start_row, move.start_col] = nothing
            end
        end
    else
        # Handle all other moves
        new_squares[move.end_row, move.end_col] = moving_piece
        new_squares[move.start_row, move.start_col] = nothing
    end

    # Update castling rights
    new_castling_rights = board.castlingrights
    if moving_piece.type == 'K'
        if moving_piece.color == 'w'
            new_castling_rights = replace(new_castling_rights, 'K' => "", 'Q' => "")
        else
            new_castling_rights = replace(new_castling_rights, 'k' => "", 'q' => "")
        end
    elseif moving_piece.type == 'R'
        if moving_piece.color == 'w'
            if move.start_row == 8 && move.start_col == 8
                new_castling_rights = replace(new_castling_rights, 'K' => "")
            elseif move.start_row == 8 && move.start_col == 1
                new_castling_rights = replace(new_castling_rights, 'Q' => "")
            end
        else
            if move.start_row == 1 && move.start_col == 8
                new_castling_rights = replace(new_castling_rights, 'k' => "")
            elseif move.start_row == 1 && move.start_col == 1
                new_castling_rights = replace(new_castling_rights, 'q' => "")
            end
        end
    end

    captured_piece_at_dest = piece_at(board, move.end_row, move.end_col)
    if captured_piece_at_dest !== nothing && captured_piece_at_dest.type == 'R'
        if captured_piece_at_dest.color == 'w'
            if move.end_row == 8 && move.end_col == 8
                new_castling_rights = replace(new_castling_rights, 'K' => "")
            elseif move.end_row == 8 && move.end_col == 1
                new_castling_rights = replace(new_castling_rights, 'Q' => "")
            end
        else
            if move.end_row == 1 && move.end_col == 8
                new_castling_rights = replace(new_castling_rights, 'k' => "")
            elseif move.end_row == 1 && move.end_col == 1
                new_castling_rights = replace(new_castling_rights, 'q' => "")
            end
        end
    end

    if isempty(new_castling_rights)
        new_castling_rights = "-"
    end

    # Handle en passant
    new_enpassant = (0, 0)
    if moving_piece.type == 'P' && abs(move.start_row - move.end_row) == 2
        new_enpassant = (move.end_row + (moving_piece.color == 'w' ? 1 : -1), move.end_col)
    else
        new_enpassant = board.enpassant
    end

    new_turn = (board.turn == 'w' ? 'b' : 'w')
    return Board(new_squares, new_turn, new_castling_rights, new_enpassant,
                 board.halfmoveclock + 1, board.fullmoveclock + (new_turn == 'b' ? 1 : 0))
end

""" Finds the 1-based (row, col) of the king of the specified color on the board. """
function findking(board::Board, king_color::Char)::Union{Tuple{Int, Int}, Nothing}
    for r in 1:8
        for c in 1:8
            piece = piece_at(board, r, c)
            if piece !== nothing && piece.type == 'K' && piece.color == king_color
                return (r, c)
            end
        end
    end
    return nothing
end

""" Checks if a given square (row, col) is attacked by any piece of attacking_color """
function issquareattacked(board::Board, row::Int, col::Int, attacking_color::Char)::Bool
    opponent_color = attacking_color
    pawn_dir = (opponent_color == 'w' ? -1 : 1)
    for dc in [-1, 1]
        target_row, target_col = row + pawn_dir, col + dc
        if isvalidsquare(target_row, target_col)
            piece = piece_at(board, target_row, target_col)
            if piece !== nothing && piece.color == opponent_color && piece.type == 'P'
                return true
            end
        end
    end

    knight_moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)]
    for (dr, dc) in knight_moves
        target_row, target_col = row + dr, col + dc
        if isvalidsquare(target_row, target_col)
            piece = piece_at(board, target_row, target_col)
            if piece !== nothing && piece.color == opponent_color && piece.type == 'N'
                return true
            end
        end
    end

    straight_dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
    for (dr, dc) in straight_dirs
        for i in 1:7
            target_row, target_col = row + i * dr, col + i * dc
            if !isvalidsquare(target_row, target_col)
                break
            end
            piece = piece_at(board, target_row, target_col)
            if piece !== nothing
                if piece.color == opponent_color && (piece.type == 'R' || piece.type == 'Q')
                    return true
                else
                    break
                end
            end
        end
    end

    diagonal_dirs = [(1, 1), (1, -1), (-1, 1), (-1, -1)]
    for (dr, dc) in diagonal_dirs
        for i in 1:7
            target_row, target_col = row + i * dr, col + i * dc
            if !isvalidsquare(target_row, target_col)
                break
            end
            piece = piece_at(board, target_row, target_col)
            if piece !== nothing
                if piece.color == opponent_color && (piece.type == 'B' || piece.type == 'Q')
                    return true
                else
                    break
                end
            end
        end
    end

    king_moves = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
    for (dr, dc) in king_moves
        target_row, target_col = row + dr, col + dc
        if isvalidsquare(target_row, target_col)
            piece = piece_at(board, target_row, target_col)
            if piece !== nothing && piece.color == opponent_color && piece.type == 'K'
                return true
            end
        end
    end
    return false
end

""" True if the color king is currently in check on the given board. """
function incheck(board::Board, color::Char)::Bool
    king_pos = findking(board, color)
    if king_pos === nothing
        println("Warning: King of color $color not found on board.")
        return false
    end
    king_row, king_col = king_pos
    attacking_color = (color == 'w' ? 'b' : 'w')
    return issquareattacked(board, king_row, king_col, attacking_color)
end

""" Get legal moves for a pawn at (r, c). """
function pawnmoves(board::Board, r::Int, c::Int, piece::Piece)::Vector{Move}
    moves = Move[]
    direction = (piece.color == 'w' ? -1 : 1)
    start_rank = (piece.color == 'w' ? 7 : 2)

    target_row = r + direction
    if isvalidsquare(target_row, c) && piece_at(board, target_row, c) === nothing
        if target_row == 1 || target_row == 8 # Promotion row
            for promotion_piece in ['Q', 'R', 'B', 'N']
                push!(moves, Move(r, c, target_row, c, Piece(promotion_piece, piece.color)))
            end
        else
            push!(moves, Move(r, c, target_row, c))
            if r == start_rank
                double_target_row = r + 2 * direction
                if isvalidsquare(double_target_row, c) && piece_at(board, double_target_row, c) === nothing
                    push!(moves, Move(r, c, double_target_row, c))
                end
            end
        end
    end

    for dc in [-1, 1]
        target_row, target_col = r + direction, c + dc
        if isvalidsquare(target_row, target_col)
            target_piece = piece_at(board, target_row, target_col)
            if target_piece !== nothing && target_piece.color != piece.color ||
               board.enpassant == (target_row, target_col)
                push!(moves, Move(r, c, target_row, target_col))
            end
        end
    end
    return moves
end

""" Helper for Rook, Bishop, Queen, get moves in sliding order along given directions """
function getslidingmoves(board::Board, r::Int, c::Int, piece::Piece, directions::Vector{Tuple{Int, Int}})::Vector{Move}
    moves = Move[]
    for (dr, dc) in directions
        for i in 1:7
            target_row, target_col = r + i * dr, c + i * dc
            if !isvalidsquare(target_row, target_col)
                break
            end
            target_piece = piece_at(board, target_row, target_col)
            if target_piece === nothing
                push!(moves, Move(r, c, target_row, target_col))
            elseif target_piece.color != piece.color
                push!(moves, Move(r, c, target_row, target_col))
                break
            else
                break
            end
        end
    end
    return moves
end

""" Generate moves for a rook at (r, c). """
function rookmoves(board::Board, r::Int, c::Int, piece::Piece)::Vector{Move}
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
    return getslidingmoves(board, r, c, piece, directions)
end

""" Generate moves for a bishop at (r, c). """
function bishopmoves(board::Board, r::Int, c::Int, piece::Piece)::Vector{Move}
    directions = [(1, 1), (1, -1), (-1, 1), (-1, -1)]
    return getslidingmoves(board, r, c, piece, directions)
end

""" Generate moves for a queen at (r, c). """
function queenmoves(board::Board, r::Int, c::Int, piece::Piece)::Vector{Move}
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)]
    return getslidingmoves(board, r, c, piece, directions)
end

""" Generate moves for a knight at (r, c). """
function knightmoves(board::Board, r::Int, c::Int, piece::Piece)::Vector{Move}
    moves = Move[]
    knight_offsets = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)]
    for (dr, dc) in knight_offsets
        target_row, target_col = r + dr, c + dc
        if isvalidsquare(target_row, target_col)
            target_piece = piece_at(board, target_row, target_col)
            if target_piece === nothing || target_piece.color != piece.color
                push!(moves, Move(r, c, target_row, target_col))
            end
        end
    end
    return moves
end

""" Generate moves for a king at (r, c) """
function kingmoves(board::Board, r::Int, c::Int, piece::Piece)::Vector{Move}
    moves = Move[]
    king_offsets = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
    for (dr, dc) in king_offsets
        target_row, target_col = r + dr, c + dc
        if isvalidsquare(target_row, target_col)
            target_piece = piece_at(board, target_row, target_col)
            if target_piece === nothing || target_piece.color != piece.color
                push!(moves, Move(r, c, target_row, target_col))
            end
        end
    end

    king_color = piece.color
    opponent_color = (king_color == 'w' ? 'b' : 'w')
    if incheck(board, king_color)
        return moves
    end
    initial_king_row = (king_color == 'w' ? 8 : 1)
    if !(r == initial_king_row && c == 5)
        return moves
    end

    if king_color == 'w' && 'K' in board.castlingrights
        if piece_at(board, initial_king_row, 6) === nothing && piece_at(board, initial_king_row, 7) === nothing
            rook = piece_at(board, initial_king_row, 8)
            if rook !== nothing && rook.type == 'R' && rook.color == king_color
                if !issquareattacked(board, initial_king_row, 5, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 6, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 7, opponent_color)
                    push!(moves, Move(r, c, initial_king_row, 7))
                end
            end
        end
    end

    if king_color == 'w' && 'Q' in board.castlingrights
        if piece_at(board, initial_king_row, 2) === nothing && piece_at(board, initial_king_row, 3) === nothing &&
           piece_at(board, initial_king_row, 4) === nothing
            rook = piece_at(board, initial_king_row, 1)
            if rook !== nothing && rook.type == 'R' && rook.color == king_color
                if !issquareattacked(board, initial_king_row, 5, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 4, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 3, opponent_color)
                    push!(moves, Move(r, c, initial_king_row, 3))
                end
            end
        end
    end

    if king_color == 'b' && 'k' in board.castlingrights
        if piece_at(board, initial_king_row, 6) === nothing && piece_at(board, initial_king_row, 7) === nothing
            rook = piece_at(board, initial_king_row, 8)
            if rook !== nothing && rook.type == 'R' && rook.color == king_color
                if !issquareattacked(board, initial_king_row, 5, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 6, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 7, opponent_color)
                    push!(moves, Move(r, c, initial_king_row, 7))
                end
            end
        end
    end

    if king_color == 'b' && 'q' in board.castlingrights
        if piece_at(board, initial_king_row, 2) === nothing && piece_at(board, initial_king_row, 3) === nothing &&
           piece_at(board, initial_king_row, 4) === nothing
            rook = piece_at(board, initial_king_row, 1)
            if rook !== nothing && rook.type == 'R' && rook.color == king_color
                if !issquareattacked(board, initial_king_row, 5, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 4, opponent_color) &&
                   !issquareattacked(board, initial_king_row, 3, opponent_color)
                    push!(moves, Move(r, c, initial_king_row, 3))
                end
            end
        end
    end

    return moves
end

""" Gets all legal moves for the current player on the given board. """
function getlegalmoves(board::Board)::Vector{Move}
    allmoves = Move[]
    movingplayercolor = board.turn
    for r in 1:8
        for c in 1:8
            piece = piece_at(board, r, c)
            if piece !== nothing && piece.color == movingplayercolor
                if piece.type == 'P'
                    append!(allmoves, pawnmoves(board, r, c, piece))
                elseif piece.type == 'R'
                    append!(allmoves, rookmoves(board, r, c, piece))
                elseif piece.type == 'N'
                    append!(allmoves, knightmoves(board, r, c, piece))
                elseif piece.type == 'B'
                    append!(allmoves, bishopmoves(board, r, c, piece))
                elseif piece.type == 'Q'
                    append!(allmoves, queenmoves(board, r, c, piece))
                elseif piece.type == 'K'
                    append!(allmoves, kingmoves(board, r, c, piece))
                end
            end
        end
    end
    legal_moves = Move[]
    for mov in unique(allmoves)
        temp_board = move!(board, mov)
        if !incheck(temp_board, movingplayercolor)
            push!(legal_moves, mov)
        end
    end
    return legal_moves
end

""" Test various chess positions and legal moves """
function testchess()
    fen_start = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
    board_start = parseFEN(fen_start)
    println("--- Starting Position ---")
    print(board_start)
    moves_start = getlegalmoves(board_start)
    println("\nLegal moves for White:")
    if isempty(moves_start)
        println("No legal moves.")
    else
        for mov in moves_start
            println("- $mov")
        end
    end
    println("Total legal moves: $(length(moves_start))")
    println("\n" * "="^40 * "\n")

    fen_check = "4k3/8/8/7Q/8/8/8/4K3 b - - 0 1"
    board_check = parseFEN(fen_check)
    println("--- Position with Black in Check ---")
    print(board_check)
    println("\nIs Black king in check? $(incheck(board_check, 'b'))")
    moves_check = getlegalmoves(board_check)
    println("\nLegal moves for Black (to get out of check or make other moves):")
    if isempty(moves_check)
        println("No legal moves. (This might indicate checkmate or stalemate)")
    else
        for mov in moves_check
            println("- $mov")
        end
    end
    println("Total legal moves: $(length(moves_check))")
    println("\n" * "="^40 * "\n")

    fen_midgame = "r3k2r/p1ppqpb1/bn2pn1p/3PP1p1/1p2P3/2N2N2/PPPBBPPP/R2QK2R w KQkq - 0 1"
    board_midgame = parseFEN(fen_midgame)
    println("--- Mid-game Position (White to move) ---")
    print(board_midgame)
    moves_midgame = getlegalmoves(board_midgame)
    println("\nLegal moves for White:")
    if isempty(moves_midgame)
        println("No legal moves.")
    else
        for mov in moves_midgame
            println("- $mov")
        end
    end
    println("Total legal moves: $(length(moves_midgame))")
    println("\n" * "="^40 * "\n")

    fen_castling_white = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"
    board_castling_white = parseFEN(fen_castling_white)
    println("--- Position Allowing White Castling ---")
    print(board_castling_white)
    moves_castling_white = getlegalmoves(board_castling_white)
    println("\nLegal moves for White (including castling):")
    if isempty(moves_castling_white)
        println("No legal moves.")
    else
        for mov in moves_castling_white
            println("- $mov")
        end
    end
    println("Total legal moves: $(length(moves_castling_white))")
    println("\n" * "="^40 * "\n")

    fen_promotion = "8/4P2k/8/8/8/8/8/R3K2R w KQkq - 0 1"
    board_promotion = parseFEN(fen_promotion)
    println("--- Position Allowing Pawn Promotion ---")
    print(board_promotion)
    moves_promotion = getlegalmoves(board_promotion)
    println("\nLegal moves for White (including pawn promotions):")
    if isempty(moves_promotion)
        println("No legal moves.")
    else
        for mov in moves_promotion
            println("- $mov")
        end
    end
    println("Total legal moves: $(length(moves_promotion))")
    println("\n" * "="^40 * "\n")

    fen_enpassant = "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"
    board_enpassant = parseFEN(fen_enpassant)
    println("--- Position Allowing En Passant ---")
    print(board_enpassant)
    moves_including_enpassant = getlegalmoves(board_enpassant)
    println("\nLegal moves for White (including en passant capture):")
    if isempty(moves_including_enpassant)
        println("No legal moves.")
    else
        for mov in moves_including_enpassant
            println("- $mov")
        end
    end
    println("Total legal moves: $(length(moves_including_enpassant))")
    println("\n" * "="^40 * "\n")

    fen_stalemate = "8/8/8/8/8/6k1/5b2/7K w - - 12 95"
    board_stalemate = parseFEN(fen_stalemate)
    println("--- Stalemate Position ---")
    print(board_stalemate)
    moves_stalemate = getlegalmoves(board_stalemate)
    println("\nLegal moves for White (should be none):")
    if isempty(moves_stalemate)
        println("No legal moves. (This indicates stalemate)")
    else
        for mov in moves_stalemate
            println("- $mov")
        end
    end
    println("Total legal moves: $(length(moves_stalemate))")
    println("\n" * "="^40 * "\n")
end

# Run the main test function
testchess()
Output:
--- Starting Position ---
  a b c d e f g h
 +-----------------+
8| r n b q k b n r |8
7| p p p p p p p p |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| P P P P P P P P |2
1| R N B Q K B N R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White:
- a2a3
- a2a4
- b2b3
- b2b4
- c2c3
- c2c4
- d2d3
- d2d4
- e2e3
- e2e4
- f2f3
- f2f4
- g2g3
- g2g4
- h2h3
- h2h4
- b1a3
- b1c3
- g1f3
- g1h3
Total legal moves: 20

========================================

--- Position with Black in Check ---
  a b c d e f g h
 +-----------------+
8| . . . . k . . . |8
7| . . . . . . . . |7
6| . . . . . . . . |6
5| . . . . . . . Q |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| . . . . . . . . |2
1| . . . . K . . . |1
 +-----------------+
  a b c d e f g h
Current turn: Black
Castling rights: -

Is Black king in check? true

Legal moves for Black (to get out of check or make other moves):
- e8d8
- e8f8
- e8d7
- e8e7
Total legal moves: 4

========================================

--- Mid-game Position (White to move) ---
  a b c d e f g h
 +-----------------+
8| r . . . k . . r |8
7| p . p p q p b . |7
6| b n . . p n . p |6
5| . . . P P . p . |5
4| . p . . P . . . |4
3| . . N . . N . . |3
2| P P P B B P P P |2
1| R . . Q K . . R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White:
- d5d6
- d5e6
- e5f6
- c3b5
- c3a4
- c3b1
- f3g5
- f3d4
- f3h4
- f3g1
- a2a3
- a2a4
- b2b3
- d2c1
- d2e3
- d2f4
- d2g5
- e2f1
- e2d3
- e2c4
- e2b5
- e2a6
- g2g3
- g2g4
- h2h3
- h2h4
- a1b1
- a1c1
- d1c1
- d1b1
- e1f1
- e1g1
- h1g1
- h1f1
Total legal moves: 34

========================================

--- Position Allowing White Castling ---
  a b c d e f g h
 +-----------------+
8| r . . . k . . r |8
7| . . . . . . . . |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| . . . . . . . . |2
1| R . . . K . . R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White (including castling):
- a1b1
- a1c1
- a1d1
- a1a2
- a1a3
- a1a4
- a1a5
- a1a6
- a1a7
- a1a8
- e1d2
- e1e2
- e1f2
- e1d1
- e1f1
- e1g1
- e1c1
- h1g1
- h1f1
- h1h2
- h1h3
- h1h4
- h1h5
- h1h6
- h1h7
- h1h8
Total legal moves: 26

========================================

--- Position Allowing Pawn Promotion ---
  a b c d e f g h
 +-----------------+
8| . . . . . . . . |8
7| . . . . P . . k |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| . . . . . . . . |2
1| R . . . K . . R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White (including pawn promotions):
- e7e8
- e7e8
- e7e8
- e7e8
- a1b1
- a1c1
- a1d1
- a1a2
- a1a3
- a1a4
- a1a5
- a1a6
- a1a7
- a1a8
- e1d2
- e1e2
- e1f2
- e1d1
- e1f1
- e1g1
- e1c1
- h1g1
- h1f1
- h1h2
- h1h3
- h1h4
- h1h5
- h1h6
- h1h7
Total legal moves: 29

========================================

--- Position Allowing En Passant ---
  a b c d e f g h
 +-----------------+
8| r n b q k b n r |8
7| p p p . p . p p |7
6| . . . . . . . . |6
5| . . . p P p . . |5
4| . . . . . . . . |4
3| . . . . . . . . |3
2| P P P P . P P P |2
1| R N B Q K B N R |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: KQkq

Legal moves for White (including en passant capture):
- e5e6
- e5f6
- a2a3
- a2a4
- b2b3
- b2b4
- c2c3
- c2c4
- d2d3
- d2d4
- f2f3
- f2f4
- g2g3
- g2g4
- h2h3
- h2h4
- b1a3
- b1c3
- d1e2
- d1f3
- d1g4
- d1h5
- e1e2
- f1e2
- f1d3
- f1c4
- f1b5
- f1a6
- g1f3
- g1h3
- g1e2
Total legal moves: 31

========================================

--- Stalemate Position ---
  a b c d e f g h
 +-----------------+
8| . . . . . . . . |8
7| . . . . . . . . |7
6| . . . . . . . . |6
5| . . . . . . . . |5
4| . . . . . . . . |4
3| . . . . . . k . |3
2| . . . . . b . . |2
1| . . . . . . . K |1
 +-----------------+
  a b c d e f g h
Current turn: White
Castling rights: -

Legal moves for White (should be none):
No legal moves. (This indicates stalemate)
Total legal moves: 0

========================================

Primarily written to see if I could find all moves with one regex. The answer was "mostly", the main problem being some moves require history (the current state of the board is not sufficient for castling and en passant). I also wanted to try different methods of making moves. It does not play well, but then neither do I.

#!/usr/bin/perl

use strict;
use warnings;
use Tk;
use Tk::ROText;
use List::Util qw( any sum0 shuffle first );

my $startingposition = our $board = <<END;
rnbqkbnr
pppppppp
--------
--------
--------
--------
PPPPPPPP
RNBQKBNR
END

my $size = 80;
my $message = 'Initializing...';
my ($from, $moving, $over);
my (%legal, %canmove, @previous, $castleleft, $castleright, $enpassant);
my @location = map { my $row = $_; map "$_$row", 'a' .. 'i' } reverse 1 .. 8;
my %values = qw( - 0 p 100 n 350 b 350 r 525 q 1e3 k 1e4);
my %names = qw(p pawn r rook n knight b bishop q queen k king);
our @moves;

my $g = qr/.{8}/s;
my $gm = qr/.{9}/s;
my $gp = qr/.{7}/s;
my $gpp = qr/.{6}/s;

my $opp = qr/[a-z]/;
my $oppe = qr/[a-z-]/;
my $whitemoves = qr/(?|
    (?| # forward
      (Q|R) (?: -* | $g (?:- $g)* ) ($oppe) # rectangular
    | (Q|B) (?: $gp (?:- $gp)* | $gm (?:- $gm)* ) ($oppe) # diagonal
    | (K) (?: | $gp | $g | $gm ) ($oppe)
    | (N) (?: $gm . $g | $gp . $g | . $gm | $gpp (?=..) ) ($oppe)
    ) (?{ push @moves, [$1, @-[1,2], $2] })
  |
    (?| # backward
      ($oppe) (?: -* | $g (?:- $g)* ) (Q|R) # rectangular
    | ($oppe) (?: $gp (?:- $gp)* | $gm (?:- $gm)* ) (Q|B) # diagonal
    | ($oppe) (?: | $gp | $g | $gm ) (K)
    | ($oppe) (?: $gm . $g | $gp . $g | . $gm | $gpp (?=..) ) (N)
    | (-) $g (P)
    | ($opp) (?: $gm | $gp ) (P)
    ) (?{ push @moves, [$2, @-[2,1], $1] })
  |
    (-) $g (-) $g (P) .*\n.{8}$ (?{ push @moves, [$3, @-[3,1], $1, $-[2]] })
  ) (*FAIL) /x;

$opp = qr/[A-Z]/;
$oppe = qr/[A-Z-]/;
my $blackmoves = qr/(?|
    (?| # forward
      (q|r) (?: -* | $g (?:- $g)* ) ($oppe) # rectangular
    | (q|b) (?: $gp (?:- $gp)* | $gm (?:- $gm)* ) ($oppe) # diagonal
    | (k) (?: | $gp | $g | $gm ) ($oppe)
    | (n) (?: $gm . $g | $gp . $g | . $gm | $gpp (?=..) ) ($oppe)
    | (p) $g (-)
    | (p) (?: $gm | $gp ) ($opp)
    ) (?{ push @moves, [$1, @-[1,2], $2] })
  |
    ^.{8}\n.* (p) $g (-) $g (-) (?{ push @moves, [$1, @-[1,3], $3, $-[2]] })
  |
    (?| # backward
      ($oppe) (?: -* | $g (?:- $g)* ) (q|r) # rectangular
    | ($oppe) (?: $gp (?:- $gp)* | $gm (?:- $gm)* ) (q|b) # diagonal
    | ($oppe) (?: | $gp | $g | $gm ) (k)
    | ($oppe) (?: $gm . $g | $gp . $g | . $gm | $gpp (?=..) ) (n)
    ) (?{ push @moves, [$2, @-[2,1], $1] })
  ) (*FAIL) /x;

my $mw = MainWindow->new;
$mw->title( 'Chess' );
my $label = $mw->Label( -textvariable => \$message, -font => 'times 20',
  )->pack(-fill => 'x');
$mw->Frame(-height => 20, -bg => 'darkblue',
  )->pack(-fill => 'x', -expand => 1);
my $grid = $mw->Frame->pack;
my @squares = map { my $me = $_; $_ % 9 == 8 ? 'oops' :
  do { my $w = $grid->Canvas( -width => $size, -height => $size,
    -bd => 0, -relief => 'flat', -highlightthickness => 0,
    -bg => ($_ % 9 + int $_ / 9) % 2 ? 'brown3' : 'brown2',
    )->grid( -row => 1 + int $_ / 9, -column => 1 + $_ % 9 );
    $w->Tk::bind('<ButtonRelease-4>' => sub{$w->yviewMoveto(0)} );
    $w->Tk::bind('<ButtonRelease-5>' => sub{$w->yviewMoveto(0)} );
    $w->Tk::bind('<1>' => sub { click($me) } );
    $w }
  } 0 .. 71;
for my $n (0 .. 7)
  {
  $grid->Label(-text => $n + 1,
    )->grid( -row => 8 - $n, -column => $_) for 0, 9;
  $grid->Label(-text => ('a' ... 'h')[$n],
    )->grid( -row => $_, -column => 1 + $n) for 0, 9;
  }
$mw->Frame(-height => 20, -bg => 'darkblue',
  )->pack(-fill => 'x', -expand => 1);
$mw->Button(-text => $_->[0], -command => $_->[1], -font => 24,
  )->pack( -side => 'left', -fill => 'x', -expand => 1) for
    [Restart => \&restart],
    ['Previous State' => \&previous],
    ['Random Move' => \&random],
    [Help => \&help],
    [Exit => sub {$mw->destroy}];

restart();

MainLoop;
-M $0 < 0 and exec $0, @ARGV;

sub restart
  {
  $from = $over = undef;
  $enpassant = -1;
  $castleleft = $castleright = 1;
  @previous =
    [ ($board = $startingposition), $castleleft, $castleright, $enpassant ];
  show( $board );
  $message = (incheck($board, 1) && "** IN CHECK ** ") . 'White to move';
  $label->configure(-bg => 'gray85');
  $label->configure(-fg => 'black');
  findlegal();
  }

sub previous
  {
  $over and $mw->bell, return;
  @previous and ($board, $castleleft, $castleright, $enpassant) =
    @{ pop @previous };
  show($board);
  findlegal();
  }

sub random
  {
  $over || keys %legal == 0 and $mw->bell, return;
  ($from, my $to) = split ' ', (keys %legal)[rand keys %legal];
# ($from, my $to) = map @$_,
#   (sort { $values{substr $board, $b->[1], 1} <=>
#     $values{substr $board, $a->[1], 1} }
#   map [ split ],
#   shuffle keys %legal)[0];
  $moving = substr $board, $from, 1;
  click($to);
  }

sub click
  {
  $over and $mw->bell, return;
  my $pos = shift;
  my $piece = substr $board, $pos, 1;
  if( defined $from )
    {
    if( $piece eq 'R' and $legal{"$from $pos"} and $from == 67 and
      $pos == 63 || $pos == 70 ) # castle
      {
      push @previous, [ $board, $castleleft, $castleright, $enpassant ];
      $pos == 63 ? $board =~ s/R---K/--KR-/ : $board =~ s/K--R/-RK-/;
      $castleright = $castleleft = 0;
      playblack();
      }
    elsif( $pos == $enpassant and $piece eq '-' and
      $from == $enpassant + 8 and substr($board, $from, 1) eq 'P' )
      {
      substr($board, $enpassant, 10) =~ s/-(.{7})Pp/P$1--/s
        or die "enpassant";
      $enpassant = -1;
      playblack();
      }
    elsif( $pos == $enpassant and $piece eq '-' and
      $from == $enpassant + 10 and substr($board, $from, 1) eq 'P' )
      {
      substr($board, $enpassant, 11) =~ s/-(.{8})pP/P$1--/s
        or die "enpassant";
      $enpassant = -1;
      playblack();
      }
    elsif( $piece =~ /[a-z-]/ and $legal{"$from $pos"} )
      {
      push @previous, [ $board, $castleleft, $castleright, $enpassant ];
      substr $board, $from, 1, '-';
      substr $board, $pos, 1, $moving;
      1 while $board =~ s/^.*\KP/Q/; # promotion
      $board =~ s/p(?=.*$)/q/g; # promotion
      $from == 67 and $castleleft = $castleright = 0; # no castle king
      $from == 63 and $castleleft = 0; # left rook
      $from == 70 and $castleright = 0; # right rook
      playblack();
      }
    else { $mw->bell }
    $from = $piece = undef;
    if( not $over )
      {
      $message = 'White to move';
      $label->configure(-bg => 'gray85');
      findlegal();
      if( ! $over and incheck($board, 1) )
        {
        $message =~ s/^/** IN CHECK ** /;
        $label->configure(-bg => 'yellow');
        }
      }
    show($board);
    }
  elsif( $piece =~ /[A-Z]/ and $canmove{$pos} )
    {
    $from = $pos;
    $moving = $piece;
    $message = "White moving $names{lc $moving} from $location[$from]";
    $squares[$from]->itemconfigure('all', -fill => 'yellow');
    }
  else
    {
    $piece =~ /[A-Z]/ and $mw->bell;
    $from = $piece = undef;
    findlegal();
    $message = 'White to move';
    show($board);
    }
  }

sub scale { map $size * $_ >> 3, @_ };

sub show
  {
  while( $board =~ /./g )
    {
    my $c = $squares[ my $pos = $-[0] ];
    my $char = uc $&;
    my $color = $& =~ /[A-Z]/ ? 'white' : 'black';
    $c->delete('all');
    if( $char eq 'P' )
      {
      $c->createOval(scale(3, 3, 5, 5));
      $c->createArc(scale(2, 4.8, 6, 9), -start => 0, -extent => 180);
      $c->itemconfigure('all', -outline => undef);
      }
    elsif( $char eq 'N' )
      {
      $c->createPolygon( scale(2, 7, 1, 4, 3, 1, 7, 4, 6, 5, 4, 4, 5.5, 7));
      }
    elsif( $char eq 'K' )
      {
      $c->createPolygon( scale(1, 7, 3.5, 4, 3.5, 3, 2.5, 3, 2.5,
        2, 3.5, 2, 3.5, 1, 4.5, 1, 4.5, 2, 5.5, 2, 5.5, 3, 4.5, 3, 4.5,
        4, 7, 7));
      $c->createArc( scale(1, 4, 4, 10), -start => 60, -extent => 120,
        -outline => undef);
      $c->createArc( scale(4, 4, 7, 10), -start => 0, -extent => 120,
        -outline => undef);
      }
    elsif( $char eq 'Q' )
      {
      $c->createPolygon( scale(2, 7, 1, 2, 3, 5, 4, 1, 5, 5, 7, 2, 6, 7));
      }
    elsif( $char eq 'R' )
      {
      $c->createPolygon( scale(1, 7, 2, 3, 1, 3, 1, 1,
        2, 1, 2, 2, 3, 2, 3, 1, 5, 1, 5, 2, 6, 2, 6, 1,
        7, 1, 7, 3, 6, 3, 7, 7));
      }
    elsif( $char eq 'B' )
      {
      $c->createPolygon(scale(3, 7, 2, 6, 4, 1, 6, 6, 5, 7));
      $c->createOval(scale(3.5, 1, 4.5, 2), -outline => undef);
      }
    $c->itemconfigure('all', -fill => $color);
    }
  }

sub newboard
  {
  my ($piece, $from, $to, $was) = @{ +shift };
  my $newboard = $board;
  substr $newboard, $from, 1, '-';
  substr $newboard, $to, 1, $piece;
  $newboard;
  }

sub incheck # board, 1=whiteincheck 0=blackincheck
  {
  my ($newboard, $who) = @_;
  local @moves;
  $newboard =~ ( $who ? $blackmoves : $whitemoves );
  any { $_->[3] =~ /k/i } @moves;
  }

sub blink
  {
  my $pos = shift;
  $message = 'Black moving...';
  for ( ('green', 'red') x 2 )
    {
    $squares[$pos]->itemconfigure('all', -fill => $_);
    $mw->update;
    select undef, undef, undef, 0.1;
    }
  }

sub findlegal
  {
  local @moves;
  $board =~ $whitemoves;
  %legal = %canmove = ();
  $legal{ $_->[1] . ' ' . $_->[2] } = $canmove{$_->[1]} = 1 for
    grep { ! incheck(newboard($_), 1) } @moves;
  @moves = ();
  if( $castleleft and $board =~ /R---K...\n\z/ )
    {
    $board =~ $blackmoves;
    my $attack = any { $_->[2] =~ /6[567]/ } @moves;
    $attack or $legal{"67 63"} = $canmove{67} = 1;
    }
  if( $castleright and $board =~ /K--R\n\z/ )
    {
    @moves or $board =~ $blackmoves;
    my $attack = any { $_->[2] =~ /6[789]/ } @moves;
    $attack or $legal{"67 70"} = $canmove{67} = 1;
    }
  if( $enpassant > 0 )
    {
    substr($board, $enpassant + $_, 1) eq 'P' and
      $legal{$enpassant + $_ . " $enpassant"} = $canmove{$enpassant + $_} = 1
      for 8, 10;
    }
  if( not %legal )
    {
    $over = 1;
    $message = incheck($board, 1) ? "CHECKMATE" : "DRAW";
    $label->configure(-bg => 'red');
    $label->configure(-fg => 'white');
    }
  }

sub islegal { $legal{"@_"} }

sub score
  {
  my $bb = newboard(shift);
  sum0 map(-$values{+lc}, $bb =~ /[A-Z]/g), map $values{$_}, $bb =~ /[a-z]/g;
  }

sub lookahead
  {
  my $bb = shift;
  local @moves;
  $bb =~ $blackmoves;
# print "black moves : " . @moves, "\n";
  my @bbest;
  for my $bmove ( @moves )
    {
    my $freedom;
    local $board = newboard($bmove);
    local @moves;
    $board =~ $whitemoves;
    $freedom = @moves;
    my @wbest;
    for my $wmove ( @moves )
      {
      local $board = newboard($wmove);
      local @moves;
      $board =~ $blackmoves;
      my @bbest2;
      for my $bmove2 ( @moves )
        {
        push @bbest2, [ $bmove, score($bmove2), $freedom ];
        }
      push @wbest, first { not incheck( newboard($wmove, 0) ) }
        sort { $b->[1] <=> $a->[1] } shuffle @bbest2;
      }
    push @bbest, first { not incheck( newboard($bmove, 1) ) }
      sort { $a->[1] <=> $b->[1] }
      grep defined $_->[1],
      shuffle @wbest;
    }
  map $_->[0], sort { $b->[1] <=> $a->[1] or $b->[2] <=> $a->[2] }
    grep defined $_->[1],
    shuffle @bbest;
  }

sub playblack
  {
  show($board);
  $message = 'Black thinking...';
  $label->configure(-bg => 'gray85');
  $mw->update;
  @moves = lookahead($board);
  my $themove = first { ! incheck(newboard($_), 0) } @moves;
  if( not $themove )
    {
    $over = 1;
    $message = incheck( $board, 0 ) ? "CHECKMATE" : "DRAW";
    $label->configure(-bg => 'red');
    $label->configure(-fg => 'white');
    return;
    }
  blink( $themove->[1] );
  $board = newboard $themove;
  $enpassant = $themove->[4] // -1;
  1 while $board =~ s/^.*\KP/Q/;
  $board =~ s/p(?=.*$)/q/g;
  show($board);
  }

sub help
  {
  my $help = $mw->Toplevel;
  $help->title("Chess Help");
  my $ro = $help->ROText( -font => 'times 14', -height => 12, -width => 60,
    )->pack;
  $help->Button(-text => 'Dismiss', -command => sub {$help->destroy},
    )->pack(-fill => 'x');
  $ro->insert(end => <<END);

You are playing White, the program is playing Black.

To move or capture : left click on piece to move,
   it should turn yellow if a legal move for that piece exists,
   then left click on the destination square.

To castle : left click on the King, then left click on a Rook.

To capture "en passant" : left click on your Pawn,
   then left click on the square the opponent's Pawn skipped over.
END
  }

Version 0.8.1+ contains demo\rosetta\chess.exw, a slightly cleaned-up copy of a 20-year old translation of TSCP.
It isn't particularly good (though perhaps a reasonable starting point for something better), at over 1,600 lines it does not really bear any useful comparison to the lisp version, and is simply not worth posting on this site, especially in light of potential copyright issues.

This implementation supports all chess rules (including castling, pawn promotion and en passant), switching sides, unlimited undo/redo, and the setup, saving and loading of board positions to/from files.

# *Board a1 .. h8
# *White *Black *WKPos *BKPos *Pinned
# *Depth *Moved *Undo *Redo *Me *You

(load "@lib/simul.l")

### Fields/Board ###
# x y color piece whAtt blAtt

(setq *Board (grid 8 8))

(for (X . Lst) *Board
   (for (Y . This) Lst
      (=: x X)
      (=: y Y)
      (=: color (not (bit? 1 (+ X Y)))) ) )

(de *Straight `west `east `south `north)

(de *Diagonal
   ((This) (: 0 1  1  0 -1  1))   # Southwest
   ((This) (: 0 1  1  0 -1 -1))   # Northwest
   ((This) (: 0 1 -1  0 -1  1))   # Southeast
   ((This) (: 0 1 -1  0 -1 -1)) ) # Northeast

(de *DiaStraight
   ((This) (: 0 1  1  0 -1  1  0 -1  1))   # South Southwest
   ((This) (: 0 1  1  0 -1  1  0  1  1))   # West Southwest
   ((This) (: 0 1  1  0 -1 -1  0  1  1))   # West Northwest
   ((This) (: 0 1  1  0 -1 -1  0 -1 -1))   # North Northwest
   ((This) (: 0 1 -1  0 -1 -1  0 -1 -1))   # North Northeast
   ((This) (: 0 1 -1  0 -1 -1  0  1 -1))   # East Northeast
   ((This) (: 0 1 -1  0 -1  1  0  1 -1))   # East Southeast
   ((This) (: 0 1 -1  0 -1  1  0 -1  1)) ) # South Southeast


### Pieces ###
(de piece (Typ Cnt Fld)
   (prog1
      (def
         (pack (mapcar '((Cls) (cdr (chop Cls))) Typ))
         Typ )
      (init> @ Cnt Fld) ) )


(class +White)
# color ahead

(dm init> (Cnt Fld)
   (=: ahead north)
   (extra Cnt Fld) )

(dm name> ()
   (pack " " (extra) " ") )

(dm move> (Fld)
   (adjMove '*White '*WKPos whAtt- whAtt+) )


(class +Black)
# color ahead

(dm init> (Cnt Fld)
   (=: color T)
   (=: ahead south)
   (extra Cnt Fld) )

(dm name> ()
   (pack '< (extra) '>) )

(dm move> (Fld)
   (adjMove '*Black '*BKPos blAtt- blAtt+) )


(class +piece)
# cnt field attacks

(dm init> (Cnt Fld)
   (=: cnt Cnt)
   (move> This Fld) )

(dm ctl> ())


(class +King +piece)

(dm name> () 'K)

(dm val> () 120)

(dm ctl> ()
   (unless (=0 (: cnt)) -10) )

(dm moves> ()
   (make
      (unless
         (or
            (n0 (: cnt))
            (get (: field) (if (: color) 'whAtt 'blAtt)) )
         (tryCastle west T)
         (tryCastle east) )
      (try1Move *Straight)
      (try1Move *Diagonal) ) )

(dm attacks> ()
   (make
      (try1Attack *Straight)
      (try1Attack *Diagonal) ) )


(class +Castled)

(dm ctl> () 30)


(class +Queen +piece)

(dm name> () 'Q)

(dm val> () 90)

(dm moves> ()
   (make
      (tryMoves *Straight)
      (tryMoves *Diagonal) ) )

(dm attacks> ()
   (make
      (tryAttacks *Straight)
      (tryAttacks *Diagonal T) ) )


(class +Rook +piece)

(dm name> () 'R)

(dm val> () 47)

(dm moves> ()
   (make (tryMoves *Straight)) )

(dm attacks> ()
   (make (tryAttacks *Straight)) )


(class +Bishop +piece)

(dm name> () 'B)

(dm val> () 33)

(dm ctl> ()
   (when (=0 (: cnt)) -10) )

(dm moves> ()
   (make (tryMoves *Diagonal)) )

(dm attacks> ()
   (make (tryAttacks *Diagonal T)) )


(class +Knight +piece)

(dm name> () 'N)

(dm val> () 28)

(dm ctl> ()
   (when (=0 (: cnt)) -10) )

(dm moves> ()
   (make (try1Move *DiaStraight)) )

(dm attacks> ()
   (make (try1Attack *DiaStraight)) )


(class +Pawn +piece)

(dm name> () 'P)

(dm val> () 10)

(dm moves> ()
   (let (Fld1 ((: ahead) (: field))  Fld2 ((: ahead) Fld1))
      (make
         (and
            (tryPawnMove Fld1 Fld2)
            (=0 (: cnt))
            (tryPawnMove Fld2 T) )
         (tryPawnCapt (west Fld1) Fld2 (west (: field)))
         (tryPawnCapt (east Fld1) Fld2 (east (: field))) ) ) )

(dm attacks> ()
   (let Fld ((: ahead) (: field))
      (make
         (and (west Fld) (link @))
         (and (east Fld) (link @)) ) ) )


### Move Logic ###
(de inCheck (Color)
   (if Color (get *BKPos 'whAtt) (get *WKPos 'blAtt)) )

(de whAtt+ (This Pce)
   (=: whAtt (cons Pce (: whAtt))) )

(de whAtt- (This Pce)
   (=: whAtt (delq Pce (: whAtt))) )

(de blAtt+ (This Pce)
   (=: blAtt (cons Pce (: blAtt))) )

(de blAtt- (This Pce)
   (=: blAtt (delq Pce (: blAtt))) )

(de adjMove (Var KPos Att- Att+)
   (let (W (: field whAtt)  B (: field blAtt))
      (when (: field)
         (put @ 'piece NIL)
         (for F (: attacks) (Att- F This)) )
      (nond
         (Fld (set Var (delq This (val Var))))
         ((: field) (push Var This)) )
      (ifn (=: field Fld)
         (=: attacks)
         (put Fld 'piece This)
         (and (isa '+King This) (set KPos Fld))
         (for F (=: attacks (attacks> This)) (Att+ F This)) )
      (reAtttack W (: field whAtt) B (: field blAtt)) ) )

(de reAtttack (W W2 B B2)
   (for This W
      (unless (memq This W2)
         (for F (: attacks) (whAtt- F This))
         (for F (=: attacks (attacks> This)) (whAtt+ F This)) ) )
   (for This W2
      (for F (: attacks) (whAtt- F This))
      (for F (=: attacks (attacks> This)) (whAtt+ F This)) )
   (for This B
      (unless (memq This B2)
         (for F (: attacks) (blAtt- F This))
         (for F (=: attacks (attacks> This)) (blAtt+ F This)) ) )
   (for This B2
      (for F (: attacks) (blAtt- F This))
      (for F (=: attacks (attacks> This)) (blAtt+ F This)) ) )

(de try1Move (Lst)
   (for Dir Lst
      (let? Fld (Dir (: field))
         (ifn (get Fld 'piece)
            (link (list This (cons This Fld)))
            (unless (== (: color) (get @ 'color))
               (link
                  (list This
                     (cons (get Fld 'piece))
                     (cons This Fld) ) ) ) ) ) ) )

(de try1Attack (Lst)
   (for Dir Lst
      (and (Dir (: field)) (link @)) )  )

(de tryMoves (Lst)
   (for Dir Lst
      (let Fld (: field)
         (loop
            (NIL (setq Fld (Dir Fld)))
            (T (get Fld 'piece)
               (unless (== (: color) (get @ 'color))
                  (link
                     (list This
                        (cons (get Fld 'piece))
                        (cons This Fld) ) ) ) )
            (link (list This (cons This Fld))) ) ) ) )

(de tryAttacks (Lst Diag)
   (use (Pce Cls Fld2)
      (for Dir Lst
         (let Fld (: field)
            (loop
               (NIL (setq Fld (Dir Fld)))
               (link Fld)
               (T
                  (and
                     (setq Pce (get Fld 'piece))
                     (<> (: color) (get Pce 'color)) ) )
               (T (== '+Pawn (setq Cls (last (type Pce))))
                  (and
                     Diag
                     (setq Fld2 (Dir Fld))
                     (= (get Fld2 'y) (get ((get Pce 'ahead) Fld) 'y))
                     (link Fld2) ) )
               (T (memq Cls '(+Knight +Queen +King)))
               (T (and Pce (xor Diag (== Cls '+Bishop)))) ) ) ) ) )

(de tryPawnMove (Fld Flg)
   (unless (get Fld 'piece)
      (if Flg
         (link (list This (cons This Fld)))
         (for Cls '(+Queen +Knight +Rook +Bishop)
            (link
               (list This
                  (cons This)
                  (cons
                     (piece (list (car (type This)) Cls) (: cnt))
                     Fld ) ) ) ) ) ) )

(de tryPawnCapt (Fld1 Flg Fld2)
   (if (get Fld1 'piece)
      (unless (== (: color) (get @ 'color))
         (if Flg
            (link
               (list This
                  (cons (get Fld1 'piece))
                  (cons This Fld1) ) )
            (for Cls '(+Queen +Knight +Rook +Bishop)
               (link
                  (list This
                     (cons (get Fld1 'piece))
                     (cons This)
                     (cons
                        (piece (list (car (type This)) Cls) (: cnt))
                        Fld1 ) ) ) ) ) )
      (let? Pce (get Fld2 'piece)
         (and
            (== Pce (car *Moved))
            (= 1 (get Pce 'cnt))
            (isa '+Pawn Pce)
            (n== (: color) (get Pce 'color))
            (link (list This (cons Pce) (cons This Fld1))) ) ) ) )

(de tryCastle (Dir Long)
   (use (Fld1 Fld2 Fld Pce)
      (or
         (get (setq Fld1 (Dir (: field))) 'piece)
         (get Fld1 (if (: color) 'whAtt 'blAtt))
         (get (setq Fld2 (Dir Fld1)  Fld Fld2) 'piece)
         (when Long
            (or
               (get (setq Fld (Dir Fld)) 'piece)
               (get Fld (if (: color) 'whAtt 'blAtt)) ) )
         (and
            (== '+Rook (last (type (setq Pce (get (Dir Fld) 'piece)))))
            (=0 (get Pce 'cnt))
            (link
               (list This
                  (cons This)
                  (cons
                     (piece (cons (car (type This)) '(+Castled +King)) 1)
                     Fld2 )
                  (cons Pce Fld1) ) ) ) ) ) )

(de pinned (Fld Lst Color)
   (use (Pce L P)
      (and
         (loop
            (NIL (setq Fld (Dir Fld)))
            (T (setq Pce (get Fld 'piece))
               (and
                  (= Color (get Pce 'color))
                  (setq L
                     (make
                        (loop
                           (NIL (setq Fld (Dir Fld)))
                           (link Fld)
                           (T (setq P (get Fld 'piece))) ) ) )
                  (<> Color (get P 'color))
                  (memq (last (type P)) Lst)
                  (cons Pce L) ) ) )
         (link @) ) ) )


### Moves ###
# Move      ((p1 (p1 . f2))        . ((p1 . f1)))
# Capture   ((p1 (p2) (p1 . f2))   . ((p1 . f1) (p2 . f2)))
# Castle    ((K (K) (C . f2) (R . f4)) . ((R . f3) (K . f1)))
# Promote   ((P (P) (Q . f2))      . ((Q) (P . f1)))
# Capt/Prom ((P (p1) (P) (Q . f2)) . ((Q) (P . f1) (p1 . f2)))
(de moves (Color)
   (filter
      '((Lst)
         (prog2
            (move (car Lst))
            (not (inCheck Color))
            (move (cdr Lst)) ) )
      (mapcan
         '((Pce)
            (mapcar
               '((Lst)
                  (cons Lst
                     (flip
                        (mapcar
                           '((Mov) (cons (car Mov) (get Mov 1 'field)))
                           (cdr Lst) ) ) ) )
               (moves> Pce) ) )
         (if Color *Black *White) ) ) )

(de move (Lst)
   (if (atom (car Lst))
      (inc (prop (push '*Moved (pop 'Lst)) 'cnt))
      (dec (prop (pop '*Moved) 'cnt)) )
   (for Mov Lst
      (move> (car Mov) (cdr Mov)) ) )


### Evaluation ###
(de mate (Color)
   (and (inCheck Color) (not (moves Color))) )

(de battle (Fld Prey Attacker Defender)
   (use Pce
      (loop
         (NIL (setq Pce (mini 'val> Attacker)) 0)
         (setq Attacker (delq Pce Attacker))
         (NIL (and (asoq Pce *Pinned) (not (memq Fld @)))
            (max 0 (- Prey (battle Fld (val> Pce) Defender Attacker))) ) ) ) )

# Ref. Sargon, Dan and Kate Spracklen, Hayden 1978
(de cost (Color)
   (if (mate (not Color))
      -9999
      (setq *Pinned
         (make
            (for Dir *Straight
               (pinned *WKPos '(+Rook +Queen))
               (pinned *BKPos '(+Rook +Queen) T) )
            (for Dir *Diagonal
               (pinned *WKPos '(+Bishop +Queen))
               (pinned *BKPos '(+Bishop +Queen) T) ) ) )
      (let (Ctl 0  Mat 0  Lose 0  Win1 NIL  Win2 NIL  Flg NIL)
         (use (White Black Col Same B)
            (for Lst *Board
               (for This Lst
                  (setq White (: whAtt)  Black (: blAtt))
                  ((if Color inc dec) 'Ctl (- (length White) (length Black)))
                  (let? Val (and (: piece) (val> @))
                     (setq Col (: piece color)  Same (== Col Color))
                     ((if Same dec inc) 'Ctl (ctl> (: piece)))
                     (unless
                        (=0
                           (setq B
                              (if Col
                                 (battle This Val White Black)
                                 (battle This Val Black White) ) ) )
                        (dec 'Val 5)
                        (if Same
                           (setq
                              Lose (max Lose B)
                              Flg (or Flg (== (: piece) (car *Moved))) )
                           (when (> B Win1)
                              (xchg 'B 'Win1)
                              (setq Win2 (max Win2 B)) ) ) )
                     ((if Same dec inc) 'Mat Val) ) ) ) )
         (unless (=0 Lose) (dec 'Lose 5))
         (if Flg
            (* 4 (+ Mat Lose))
            (when Win2
               (dec 'Lose (>> 1 (- Win2 5))) )
            (+ Ctl (* 4 (+ Mat Lose))) ) ) ) )


### Game ###
(de display (Res)
   (when Res
      (disp *Board T
         '((This)
            (cond
               ((: piece) (name> @))
               ((: color) " - ")
               (T "   ") ) ) ) )
   (and (inCheck *You) (prinl "(+)"))
   Res )

(de moved? (Lst)
   (or
      (> 16 (length Lst))
      (find '((This) (n0 (: cnt))) Lst) ) )

(de bookMove (From To)
   (let Pce (get From 'piece)
      (list 0 (list (list Pce (cons Pce To)) (cons Pce From))) ) )

(de myMove ()
   (let? M
      (cadr
         (cond
            ((moved? (if *Me *Black *White))
               (game *Me *Depth moves move cost) )
            (*Me
               (if (member (get *Moved 1 'field 'x) (1 2 3 5))
                  (bookMove 'e7 'e5)
                  (bookMove 'd7 'd5) ) )
            ((rand T) (bookMove 'e2 'e4))
            (T (bookMove 'd2 'd4)) ) )
      (move (car (push '*Undo M)))
      (off *Redo)
      (cons
         (caar M)
         (cdr (asoq (caar M) (cdr M)))
         (pick cdr (cdar M)) ) ) )

(de yourMove (From To Cls)
   (when
      (find
         '((Mov)
            (and
               (== (caar Mov) (get From 'piece))
               (== To (pick cdr (cdar Mov)))
               (or
                  (not Cls)
                  (isa Cls (car (last (car Mov)))) ) ) )
         (moves *You) )
      (prog1 (car (push '*Undo @))
         (off *Redo)
         (move @) ) ) )

(de undo ()
   (move (cdr (push '*Redo (pop '*Undo)))) )

(de redo ()
   (move (car (push '*Undo (pop '*Redo)))) )

(de setup (Depth You Init)
   (setq *Depth (or Depth 5)  *You You  *Me (not You))
   (off *White *Black *Moved *Undo *Redo)
   (for Lst *Board
      (for This Lst (=: piece) (=: whAtt) (=: blAtt)) )
   (if Init
      (for L Init
         (with (piece (cadr L) 0 (car L))
            (unless (caddr L)
               (=: cnt 1)
               (push '*Moved This) ) ) )
      (mapc
         '((Cls Lst)
            (piece (list '+White Cls) 0 (car Lst))
            (piece '(+White +Pawn) 0 (cadr Lst))
            (piece '(+Black +Pawn) 0 (get Lst 7))
            (piece (list '+Black Cls) 0 (get Lst 8)) )
         '(+Rook +Knight +Bishop +Queen +King +Bishop +Knight +Rook)
         *Board ) ) )

(de main (Depth You Init)
   (setup Depth You Init)
   (display T) )

(de go Args
   (display
      (cond
         ((not Args) (xchg '*Me '*You) (myMove))
         ((== '- (car Args)) (and *Undo (undo)))
         ((== '+ (car Args)) (and *Redo (redo)))
         ((apply yourMove Args) (display T) (myMove)) ) ) )

# Print position to file
(de ppos (File)
   (out File
      (println
         (list 'main *Depth *You
            (lit
               (mapcar
                  '((This)
                     (list
                        (: field)
                        (val This)
                        (not (memq This *Moved)) ) )
                  (append *White *Black) ) ) ) ) ) )

Start:

$ pil chess.l -main +
   +---+---+---+---+---+---+---+---+
 8 |<R>|<N>|<B>|<Q>|<K>|<B>|<N>|<R>|
   +---+---+---+---+---+---+---+---+
 7 |<P>|<P>|<P>|<P>|<P>|<P>|<P>|<P>|
   +---+---+---+---+---+---+---+---+
 6 |   | - |   | - |   | - |   | - |
   +---+---+---+---+---+---+---+---+
 5 | - |   | - |   | - |   | - |   |
   +---+---+---+---+---+---+---+---+
 4 |   | - |   | - |   | - |   | - |
   +---+---+---+---+---+---+---+---+
 3 | - |   | - |   | - |   | - |   |
   +---+---+---+---+---+---+---+---+
 2 | P | P | P | P | P | P | P | P |
   +---+---+---+---+---+---+---+---+
 1 | R | N | B | Q | K | B | N | R |
   +---+---+---+---+---+---+---+---+
     a   b   c   d   e   f   g   h

Entering moves:

: (go e2 e4)

Undo moves:

: (go -)

Redo:

: (go +)

Switch sides:

: (go)

Save position to a file:

: (ppos "file")

Load position from file:

: (load "file")

Library: pygame

"Python Chess" is a chess game at the PyGame-Website and Homepage.

Library: VPython

There is a 3D-Chess-Board in the VPython contributed section.

Library: python-chess

A very simple chess engine using python-chess. The computer plays Black. The program uses a two-ply search which computes material value for both sides and its own piece mobility after Black and White have made their moves.

The default Unicode board may look wonky and misaligned with certain terminal fonts. To use an ASCII board instead (like in the output shown below), set UNICODE = False. If your terminal uses dark mode, set DARKMODE = True.

Increasing RANDFAC, e.g. to 10 or even 100, creates more variety in computer moves, so that there is less repetition in games and openings.

The computer may say some things that allude to the chess game in 2001: A Space Odyssey, by the way.

# Simple Python chess engine
# Computer plays Black

import sys, random, chess
from collections import Counter

UNICODE  = True     # Print board with Unicode symbols?
DARKMODE = False    # Invert symbol colors?
RANDFAC  = 1        # Randomness factor

board = chess.Board()

def hal9000():
    print("Thank you for a very enjoyable game.")

def pboard():
    "Print board"
    if UNICODE and DARKMODE:
        print(board.unicode(invert_color=True))
    elif UNICODE:
        print(board.unicode())
    else:
        print(board)
pboard()

while not board.outcome():
    while True:
        try:
            move = input("Your move? ")
            if move in ("q", "quit", "resign", "exit"):
                hal9000()
                sys.exit()
            board.push_uci(move)
        except ValueError: print("Sorry?")
        else: break

    moves = {}
    for mymove in board.legal_moves:
        board.push(mymove)
        if board.result() == "0-1": # Can Black win? If so, end the game.
            print(mymove)
            pboard()
            print("I'm sorry, Frank. I think you missed it:")
            pm = board.peek()
            pn = chess.piece_name(board.piece_at(pm.to_square).piece_type)
            ps = chess.square_name(pm.to_square)
            print(f"{pn.capitalize()} to {ps}, mate.")
            hal9000()
            sys.exit()

        for yourmove in board.legal_moves:
            board.push(yourmove)
            if board.result() == "1-0": # Has White won? If so, avoid move.
                board.pop()
                moves[mymove] = -1000
                break
            v = Counter(board.fen().split()[0])
            p = (9 * (v['q']-v['Q']) + 5 * (v['r']-v['R']) + 3 * (v['b']-v['B'])
                + 3 * (v['n']-v['N']) + v['p'] - v['P'])
            mobility = len(list(board.legal_moves)) + RANDFAC * random.random()
            p += mobility / 1000
            #print(mymove, yourmove, p)
            old = moves.get(mymove, 1e6)
            if p < old: moves[mymove] = p
            board.pop()
        board.pop()
    try: sel = sorted(moves.items(), key=lambda item: -item[1])[0][0]
    except: break
    print(sel)
    board.push(sel)
    pboard()

print(f"Game finished, result is {board.result()}")
hal9000()
Output:

(in ASCII)

$ python3 simplechess.py 
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R
Your move? e2e4
e7e6
r n b q k b n r
p p p p . p p p
. . . . p . . .
. . . . . . . .
. . . . P . . .
. . . . . . . .
P P P P . P P P
R N B Q K B N R
Your move? g1f3
d8f6
r n b . k b n r
p p p p . p p p
. . . . p q . .
. . . . . . . .
. . . . P . . .
. . . . . N . .
P P P P . P P P
R N B Q K B . R
Your move?
…

Here is the PGN of a full game against Stockfish. Like many simple chess engines, it has an unfortunate tendency to rush out with its Queen early in the game, because that kind of move looks wonderful to the computer in terms of maximizing piece mobility. But unfortunately the Queen is often in danger there, leading to its loss:

[Event "Stockfish vs Python World Championship"]
[Site "Rosetta Code"]
[Date "2022.05.08"]
[Round "1"]
[White "Stockfish"]
[Black "Python Simple Chess"]
[Result "1-0"]

1.e4 e6 2.Nf3 Qf6 3.Nc3 Qf4 4.d4 Qg4 5.Be2 Qxg2 6.Rg1 Qh3 7.Bg5 Nc6 8.Rg3 
Qh5 9.Nh4 Qxg5 10.Rxg5 Bd6 11.Rxg7 Nf6 12.e5 Bxe5 13.dxe5 Nxe5 14.Qd4 Ke7 
15.Qxe5 Ne8 16.Qg5+ Kd6 17.O-O-O+ Kc6 18.Qb5# 1-0

Opening play could be improved a lot by using book moves, which is easy to do in python-chess with its Polyglot support.

Here is an example of a won game against a random mover chess bot, just to show this Python engine can actually win, provided its opponent is inept enough. (Human chess beginners tend to play on a random mover level, so this is a useful benchmark.)

[Event "Rosetta Code Chess Classics"]
[Site "Rosetta Code"]
[Date "2022.05.08"]
[Round "1"]
[White "Random Mover"]
[Black "Python Simple Chess"]
[Result "0-1"]

1.Nf3 e6 2.c4 Qf6 3.Ng1 Qf4 4.h4 Qxc4 5.e3 Qe4 6.Qg4 Qxg4 
7.Nh3 Qxh4 8.d3 Bb4+ 9.Bd2 Bd6 10.a4 Nc6 11.Bc3 Qg4 12.Ba5 Nxa5 
13.Be2 Qxg2 14.Ng1 Qxh1 15.b4 Qxg1+ 16.Bf1 Bxb4+ 17.Nc3 Bxc3+ 
18.Ke2 Bxa1 19.Kd2 Qxf1 20.Kc2 Qxf2+ 21.Kb1 Qb2# 0-1
Translation of: BASIC
Library: Wren-iterate
Library: Wren-fmt
Library: Wren-ioutil
Library: Wren-str
import "./iterate" for Stepped
import "./fmt" for Fmt
import "./ioutil" for Input, Output
import "./str" for Str

var Board = List.filled(8, null)

// initialize Board
var starting = [
    [-500, -270, -300, -900, -7500, -300, -270, -500],
    [-100, -100, -100, -100,  -100, -100, -100, -100],
    [   0,    0,    0,    0,     0,    0,    0,    0],
    [   0,    0,    0,    0,     0,    0,    0,    0],
    [   0,    0,    0,    0,     0,    0,    0,    0],
    [   0,    0,    0,    0,     0,    0,    0,    0],
    [ 100,  100,  100,  100,   100,  100,  100,  100],
    [ 500,  270,  300,  900,  5000,  300,  270,  500]
]
for (x in 0..7) {
    Board[x] = List.filled(8, 0)
    for (y in 0..7) Board[x][y] = starting[x][y]
}

// best moves
var BestA = List.filled(8, 0)
var BestB = List.filled(8, 0)
var BestX = List.filled(8, 0)
var BestY = List.filled(8, 0)

// current Levels
var Cflag = false
var Level = 0
var MaxLevel = 5
var Score = 0
var End = false

// helper classes
class Terminal {
    static clear() { 
        Output.fwrite("\e[2J")
        locate(1, 1)
    }

    static locate(r, c) {
        Output.fwrite("\e[%(r);%(c)H")
    }
}

class Color {
    static set(fore, back) {
        fore = (fore < 8) ? fore + 30 : fore + 82
        back = (back < 8) ? back + 40 : back + 92
        Output.fwrite("\e[%(fore);%(back)m")
    }

    static reset() {
        Output.fwrite("\e[39;49m")
    }
}

class Chess {
    // generate list of moves for bishop
    static bishop(a, b, xx, yy, ndx) {
        var id = Board[b][a].sign

        var f = Fn.new { |x, y|
            // make sure no piece of same color
            if (id != Board[y][x].sign) {
                ndx = ndx + 1
                xx[ndx] = x
                yy[ndx] = y
            }
        }

        // work out diagonal moves in each of four directions
        for (dxy in 1..7) {
            var x = a - dxy
            var y = b + dxy
            // stop if go off the board
            if (x < 0 || x > 7 || y < 0 || y > 7) break
            f.call(x, y)
            // stop when hit a piece
            if (Board[y][x] != 0) break
        }
        for (dxy in 1..7) {
            var x = a + dxy
            var y = b + dxy
            if (x < 0 || x > 7 || y < 0 || y > 7) break
            f.call(x, y)
            if (Board[y][x] != 0) break
        }
        for (dxy in 1..7) {
            var x = a - dxy
            var y = b - dxy
            if (x < 0 || x > 7 || y < 0 || y > 7) break
            f.call(x, y)
            if (Board[y][x] != 0) break
        }
        for (dxy in 1..7) {
            var x = a + dxy
            var y = b - dxy
            if (x < 0 || x > 7 || y < 0 || y > 7) break
            f.call(x, y)
            if (Board[y][x] != 0) break
        }
        return ndx
    }

    // evaluate possible moves
    static evaluate(id, prune) {
        var xx = List.filled(27, 0)
        var yy = List.filled(27, 0)
        Level = Level + 1 // update recursion level
        var bestScore = 10000 * id
        for (b in 7..0) { // loop through each square
            for (a in 7..0) {
                // if square doesn't have right color piece, go to next square
                if (Board[b][a].sign != id) {
                    if (Level == 1) showman(a, b, 0)
                    continue
                }
                if (Level == 1) showman(a, b, 8) // show move currently being tried
                var ndx = 0
                ndx = moveList(a, b, xx, yy, ndx) // get list of moves for current piece
                for (i in Stepped.ascend(0..ndx)) { // loop through each possible move
                    var x = xx[i]
                    var y = yy[i]
                    if (Level == 1) {
                        Terminal.locate(1, 1)
                        Fmt.print("Trying: $c$d-$c$d", 65+a, 8-b, 65+x, 8-y)
                        showman(x, y, 8)
                    }
                    var oldScore = Score
                    var mover = Board[b][a]    // store these locations
                    var target = Board[y][x]   // so we can set the move back
                    makeMove(a, b, x, y)       // make the move so we can evaluate
                    if (Level < MaxLevel) {
                        var p = bestScore - target + id*(8 - (4-x).abs - (4-y).abs)
                        Score = Score + evaluate(-id, p)
                    }
                    // work out score for move
                    Score = Score + target - id*(8 - (4-x).abs - (4-y).abs)
                    if ((id < 0 && Score > bestScore) || (id > 0 && Score < bestScore)) {
                        // update current best score
                        BestA[Level] = a
                        BestB[Level] = b
                        BestX[Level] = x
                        BestY[Level] = y
                        bestScore = Score
                        if ((id < 0 && bestScore >= prune) ||
                            (id > 0 && bestScore <= prune)) {
                            // prune to avoid wasting time
                            Board[b][a] = mover  // restore position prior to modification
                            Board[y][x] = target
                            Score = oldScore
                            if (Level == 1) showman(x, y, 0)
                            if (Level == 1) showman(a, b, 0)
                            Level = Level - 1
                            return bestScore
                        }
                    }
                    Board[b][a] = mover
                    Board[y][x] = target
                    Score = oldScore
                    if (Level == 1) showman(x, y, 0)
                }
                if (Level == 1) showman(a, b, 0)
            }
        }
        Level = Level - 1
        return bestScore
    }

    // determine whether 'in check' or not
    static inCheck() {
        var xx = List.filled(27, 0)
        var yy = List.filled(27, 0)
        var ndx = 0
        for (b in 0..7) {
            for (a in 0..7) {
                if (Board[b][a] >= 0) continue
                ndx = moveList(a, b, xx, yy, ndx)
                for (i in Stepped.ascend(0..ndx)) {
                    var x = xx[i]
                    var y = yy[i]
                    if (Board[y][x] == 5000) {
                        System.print("You are in check!\n\n")
                        return true
                    }
                }
            }
        }
        return false
    }

    // get player move
    static io(a, b, x, y, result) {
        var xx = List.filled(27, 0)
        var yy = List.filled(27, 0)
        Terminal.clear()
        if (a >= 0) {
            if (result < -2500) {
                System.print("I resign")
                End = true
                return
            }
            var piece = Board[y][x]
            makeMove(a, b, x, y)
            // show computer move
            Fmt.print("My move: $c$d-$c$d", 65+a, 8-b, 65+x, 8-y)
            if (piece != 0) {
                System.write("I took your ")
                System.print( (piece == 100)  ? "pawn"   :
                              (piece == 270)  ? "knight" :
                              (piece == 300)  ? "bishop" :
                              (piece == 500)  ? "rook"   :
                              (piece == 900)  ? "queen"  :
                              (piece == 5000) ? "king"   : "")       
            }
            inCheck()
        }
        while (true) {
            showbd()
            Terminal.locate(24, 1)
            var inp = Str.upper(Input.text("Your move (ex: E2-E4): "))
            if (inp == "QUIT") {
                Terminal.clear()
                End = true
                return
            }
            // castling, kingside rook 
            if (inp == "O-O" || inp == "0-0") {
                if (Cflag || Board[7][7] != 500 ||
                    Board[7][6] != 0 || Board[7, 5] != 0) {
                    Terminal.clear()
                    continue
                }
                Board[7][6] = 5000
                Board[7][4] = 0
                Board[7][5] = 500
                Board[7][7] = 0
                Cflag = true
                return
            }
            // castling, queenside rook
            if (inp == "O-O-O" || inp == "0-0-0") {
                if (Cflag || Board[7][0] != 500 ||
                    Board[7][1] != 0 || Board[7, 2] != 0 || Board[7][3] != 0) {
                    Terminal.clear()
                    continue
                }
                Board[7][2] = 5000
                Board[7][4] = 0
                Board[7][3] = 500
                Board[7][0] = 0
                Cflag = true
                return
            }
            if (inp.count < 5) {
                Terminal.clear()
                continue
            }
            b = 8 - (inp[1].bytes[0] - 48)
            a = inp[0].bytes[0] - 65
            x = inp[3].bytes[0] - 65
            y = 8 - (inp[4].bytes[0] - 48)
            if (b > 7 || b < 0 || a > 7 || a < 0 || x > 7 || 
                x < 0 || y > 7 || y < 0 || Board[b][a] <= 0) {
                Terminal.clear()
                continue
            }
            var ndx = 0
            ndx = moveList(a, b, xx, yy, ndx)
            // validate move
            for (k in Stepped.ascend(0..ndx)) {
                if (x == xx[k] && y == yy[k]) {
                    var mover = Board[b][a]
                    var target = Board[y][x]
                    makeMove(a, b, x, y)
                    Terminal.locate(1, 1)
                    // make sure move out of check
                    if (!inCheck()) return
                    Board[b][a] = mover // otherwise move out of check and reset board
                    Board[y][x] = target
                    Terminal.clear()
                    break
                }
            }
            Terminal.clear()
        }
        return
    }

    // generate list of moves for king
    static king(a, b, xx, yy, ndx) {
        var id = Board[b][a].sign
        // go through each of 8 possible moves, checking for same color and off board
        for (dy in -1..1) {
            if (b + dy < 0 || b + dy > 7) continue
            for (dx in -1..1) {
                if (a + dx < 0 || a + dx > 7) continue
                if (id != Board[b+dy][a+dx].sign) {
                    ndx = ndx + 1
                    xx[ndx] = a + dx
                    yy[ndx] = b + dy
                }
            }
        }
        return ndx
    }

    // generate list of moves for knight
    static knight(a, b, xx, yy, ndx) {
        var id = Board[b][a].sign  // get color

        var f = Fn.new { |x, y|
            // make sure on board
            if (x < 0 || x > 7 || y < 0 || y > 7) return
            // make sure no piece of same color
            if (id != Board[y][x].sign) {
                ndx = ndx + 1
                xx[ndx] = x
                yy[ndx] = y
            }
        }

        // work out each of the knight's eight moves
        f.call(a - 1, b - 2)   
        f.call(a - 2, b - 1) 
        f.call(a + 1, b - 2)
        f.call(a + 2, b - 1)
        f.call(a - 1, b + 2)
        f.call(a - 2, b + 1)
        f.call(a + 1, b + 2)
        f.call(a + 2, b + 1)
        return ndx
    }

    // make a move on the board
    static makeMove(a, b, x, y) {
        Board[y][x] = Board[b][a] // move piece to target square
        Board[b][a] = 0           // old square now empty
        if (y == 0 && Board[y][x] ==  100) Board[y][x] =  900 // pawn promoted
        if (y == 7 && Board[y][x] == -100) Board[y][x] = -900
    }

    // generate list of moves for current piece
    static moveList(a, b, xx, yy, ndx) {
        var piece = Board[b][a].abs.truncate // get value corresponding to piece
        ndx = -1
        // call proper move listing routine depending on piece
        if (piece == 100) {
            ndx = pawn(a, b, xx, yy, ndx)
        } else if (piece == 270) {
            ndx = knight(a, b, xx, yy, ndx)
        } else if (piece == 300) {
            ndx = bishop(a, b, xx, yy, ndx)
        } else if (piece == 500) {
            ndx = rook(a, b, xx, yy, ndx)
        } else if (piece == 900) {
            ndx = queen(a, b, xx, yy, ndx)
        } else {
            ndx = king(a, b, xx, yy, ndx)
        }
        return ndx
    }

    // generate list of moves for pawn
    static pawn(a, b, xx, yy, ndx) {
        var id = Board[b][a].sign  // get color
        if (a - 1 >= 0 && a - 1 <= 7 && b - id >= 0 && b - id <= 7) {
            // if there's a piece to capture, do so
            if (Board[b-id][a-1].sign == -id) {
                ndx = ndx + 1
                xx[ndx] = a - 1
                yy[ndx] = b - id
            }
        }
        if (a + 1 >= 0 && a + 1 <= 7 && b - id >= 0 && b - id <= 7) {
            if (Board[b-id][a+1].sign == -id) {
                ndx = ndx + 1
                xx[ndx] = a + 1
                yy[ndx] = b - id
            }
        }
        if (a >= 0 && a <= 7 && b - id >= 0 && b - id <= 7) {
           // make sure square is empty
           if (Board[b-id][a] == 0) {
                ndx = ndx + 1
                xx[ndx] = a
                yy[ndx] = b - id
                if ((id < 0 && b == 1) || (id > 0 && b == 6)) {
                    // if it's empty move two squares forward
                    if (Board[b-id-id][a] == 0) {
                        ndx = ndx + 1
                        xx[ndx] = a
                        yy[ndx] = b - 2*id
                    }
                }
            }
        }
        return ndx
    }

    // generate list of moves for queen
    static queen(a, b, xx, yy, ndx) {
        // queen's move = bishop + rook
        ndx = bishop(a, b, xx, yy, ndx)
        ndx = rook(a, b, xx, yy, ndx)
        return ndx
    }

    // generate list of moves for rook
    static rook(a, b, xx, yy, ndx) {
        var id = Board[b][a].sign
        // work out vert/horiz moves in each direction
        for (x in Stepped.descend(a-1..0)) {
            if (id != Board[b][x].sign) {
                // if no piece of same color
                ndx = ndx + 1
                xx[ndx] = x
                yy[ndx] = b
            }
            if (Board[b][x] != 0) break
        }
        for (x in Stepped.ascend(a+1..7)) {
            if (id != Board[b][x].sign) {
                ndx = ndx + 1
                xx[ndx] = x
                yy[ndx] = b
            }
            if (Board[b][x] != 0) break
        }
        for (y in Stepped.descend(b-1..0)) {
            if (id != Board[y][a].sign) {
                ndx = ndx + 1
                xx[ndx] = a
                yy[ndx] = y
            }
            if (Board[y][a] != 0) break
        }
        for (y in Stepped.ascend(b+1..7)) {
            if (id != Board[y][a].sign) {
                ndx = ndx + 1
                xx[ndx] = a
                yy[ndx] = y
            }
            if (Board[y][a] != 0) break
        }
        return ndx
    }

    // show board
    static showbd() {
        Terminal.locate(3, 30)
        Color.set(7, 0)
        System.print("A  B  C  D  E  F  G  H")
        for (k in 0..25) {
            Terminal.locate(4, 28 + k)
            Color.set(3, 0)
            System.print(String.fromCodePoint(0x2584))
        }
        for (b in 0..7) {
            Terminal.locate(2*b + 5, 26)
            Color.set(7, 0)
            System.print(String.fromCodePoint(56 - b))
            Terminal.locate(2*b + 5, 28)
            Color.set(3, 0)
            System.print(String.fromCodePoint(0x2588))      
            Terminal.locate(2*b + 6, 28)
            Color.set(3, 0)
            System.print(String.fromCodePoint(0x2588))
            for (a in 0..7) {
                var colour = (((a + b) % 2) != 0) ? 8 : 9
                square(3*a + 31, 2*b + 5, colour)
            }
            Terminal.locate(2*b + 5, 53)
            Color.set(3, 0)
            System.print(String.fromCodePoint(0x2588)) 
            Terminal.locate(2*b + 6, 53)
            Color.set(3, 0)
            System.print(String.fromCodePoint(0x2588))
            Terminal.locate(2*b + 6, 55)
            Color.set(7, 0)
            System.print(String.fromCodePoint(56 - b))
        }
        for (k in 0..25) {
            Terminal.locate(21, 28 + k)
            Color.set(3, 0)
            System.print(String.fromCodePoint(0x2580))
        }
        Terminal.locate(22, 30)
        Color.set(7, 0)
        System.print("A  B  C  D  E  F  G  H")
        for (b in 0..7) {
            for (a in 0..7) showman(a, b, 0)
        }
        Color.set(7, 0)
    }

    // show piece
    static showman(a, b, flag) {
        var back = (Board[b][a] <= 0) ? 0 : 7
        var fore = 7 - back + flag
        if (Board[b][a] == 0) {
            back = (((a + b) & 1) != 0) ? 8 : 9
            fore = back + (-1) * ((flag > 0) ? -1 : 0)
        }
        var piece = Board[b][a].abs.truncate
        var n = (piece == 0)    ? String.fromCodePoint(0x2588) :
                (piece == 100)  ? "P" :
                (piece == 270)  ? "N" :
                (piece == 300)  ? "B" :
                (piece == 500)  ? "R" :
                (piece == 900)  ? "Q" :
                (piece == 5000) ? "K" :
                (piece == 7500) ? "K" : " "
        Terminal.locate(2*b + 5 - ((Board[b][a] > 0) ? -1 : 0), 3*a + 30)
        Color.set(fore, back)
        System.print(n)
        Terminal.locate(1, 1)
        Color.set(7, 0)
    }

    // display a square
    static square(a, b, c) {
        var mt = String.fromCodePoint(0x2588) * 3
        Terminal.locate(b, a - 2)
        Color.set(c, c)
        System.print(mt)
        Terminal.locate(b + 1, a - 2)
        Color.set(c, c)
        System.print(mt)
        Color.set(7, 0)
    }

    // start a game
    static start() {
        var a = -1
        var b =  0
        var x =  8
        var y =  8
        var result = 0
        while (true) {
            Score = 0
            io(a, b, x, y, result)       // get white's move
            if (End) {
                Color.reset()
                Terminal.clear()
                return
            }
            Terminal.clear()
            showbd()                     // update board to show white's move
            result = evaluate(-1, 10000) // get black's move
            a = BestA[1]                 // start column for black's move
            b = BestB[1]                 // start row for black's move
            x = BestX[1]                 // end column for black's move
            y = BestY[1]                 // end row for black's move
        }
    }
}

Chess.start()