#StackBounty: #php #chess PHP Chess Version 2

Bounty: 50

For programming practice, I created a chess program in PHP (my most comfortable language).

The program reads a FEN (a string with the location of all the pieces on the board) from the URL, generates a board, then generates all the legal moves for that position. Then when you click the move, it loads the same file but with a new ?fen= in the URL.

The legal moves list code is 100% complete. It handles all special cases, including pawn promotion, en passant, castling, not allowing moves that place/keep your own king in check, etc.

Moves can be made by double clicking on the move, hitting “Make A Move”, or drag and dropping a piece on the board.

I am hoping for feedback/advice to help me make the following improvements to the PHP code:

  • Make the code more readable / more organized / use classes better.
  • Speed up the code. Complex positions with lots of moves are taking 1500ms to render. This speed is acceptable for a browser based game, but would be way too slow if I wanted to turn this code into a chess engine (chess A.I.)
  • Find and eliminate bugs.
  • Teach me good programming practices. (I am an amateur programmer who has taken 1 computer science class in my life)

Any feedback that improves the code would be appreciated. Thank you.

Humble beginnings: Version 1 of the program can be found here.

Website

http://www.clania.net/admiraladama/chess_v2

Screenshot

Screenshot

index.php

<?php

$time = microtime();
$time = explode(' ', $time);
$time = $time[1] + $time[0];
$start = $time;

error_reporting(-1);
ini_set('display_errors', 'On');

require_once('ChessGame.php');
require_once('ChessBoard.php');
require_once('ChessPiece.php');
require_once('ChessMove.php');
require_once('ChessSquare.php');
require_once('Dictionary.php');

$game = new ChessGame();

if ( isset($_GET['reset']) ) {
    // Skip this conditional. ChessGame's FEN is the default, new game FEN and doesn't need to be set again.
} elseif ( isset($_GET['move']) ) {
    $game->board->set_fen($_GET['move']);
} elseif ( isset($_GET['fen']) ) {
    $game->board->set_fen($_GET['fen']);
}

$fen = $game->board->get_fen();
$side_to_move = $game->board->get_side_to_move_string();
$who_is_winning = $game->board->get_who_is_winning_string();
$graphical_board_array = $game->board->get_graphical_board();
$legal_moves = $game->get_legal_moves_list($game->board->color_to_move, $game->board);

require_once('view.php');

ChessGame.php

<?php

class ChessGame {
    var $board;

    function __construct($fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1') {
        $this->board = new ChessBoard($fen);
    }

    function __clone() {
        $this->board = clone $this->board;

        if ( $this->move_list ) {
            $array = array();
            foreach ( $this->move_list as $key => $move ) {
                array_push($array, clone $move);
            }
            $this->move_list = $array;
        }
    }

    function get_legal_moves_list(
        $color_to_move,
        $board,
        $eliminate_king_in_check_moves = TRUE
    ) {
        $pieces_to_check = $this->get_all_pieces_by_color($color_to_move, $board);

        $moves = array();

        // Is player to move checkmated?
        // Is enemy checkmated?
        // If so, return NULL since there are no legal moves. The game is over!
        // Else you will get a weird list of legal moves including moves capturing the enemy king.

        foreach ( $pieces_to_check as $key => $piece ) {
            if ( $piece->type == 'pawn' ) {
                if ( $piece->color == 'white' ) {
                    $directions_list = array('north');
                    if ( $piece->on_rank(2) ) {
                        $moves = $this->add_slide_moves_to_moves_list($directions_list, 2, $moves, $piece, $color_to_move, $board);
                    } else {
                        $moves = $this->add_slide_moves_to_moves_list($directions_list, 1, $moves, $piece, $color_to_move, $board);
                    }

                    $directions_list = array('northeast', 'northwest');
                    $moves = $this->add_capture_moves_to_moves_list($directions_list, $moves, $piece, $color_to_move, $board);

                    // en passant
                    $moves = $this->add_en_passant_moves_to_moves_list($piece, $board, $moves);
                } elseif ( $piece->color == 'black' ) {
                    $directions_list = array('south');
                    if ( $piece->on_rank(7) ) {
                        $moves = $this->add_slide_moves_to_moves_list($directions_list, 2, $moves, $piece, $color_to_move, $board);
                    } else {
                        $moves = $this->add_slide_moves_to_moves_list($directions_list, 1, $moves, $piece, $color_to_move, $board);
                    }

                    $directions_list = array('southeast', 'southwest');
                    $moves = $this->add_capture_moves_to_moves_list($directions_list, $moves, $piece, $color_to_move, $board);

                    // en passant
                    $moves = $this->add_en_passant_moves_to_moves_list($piece, $board, $moves);
                }
            } elseif ( $piece->type == 'knight' ) {
                $oclock_list = array(1, 2, 4, 5, 7, 8, 10, 11);

                $moves = $this->add_jump_and_jumpcapture_moves_to_moves_list($oclock_list, $moves, $piece, $color_to_move, $board);
            } elseif ( $piece->type == 'bishop' ) {
                $directions_list = array(
                    'northwest',
                    'northeast',
                    'southwest',
                    'southeast'
                );

                $moves = $this->add_slide_and_slidecapture_moves_to_moves_list($directions_list, 7, $moves, $piece, $color_to_move, $board);
            } elseif ( $piece->type == 'rook' ) {
                $directions_list = array(
                    'north',
                    'south',
                    'east',
                    'west'
                );

                $moves = $this->add_slide_and_slidecapture_moves_to_moves_list($directions_list, 7, $moves, $piece, $color_to_move, $board);
            } elseif ( $piece->type == 'queen' ) {
                $directions_list = array(
                    'north',
                    'south',
                    'east',
                    'west',
                    'northwest',
                    'northeast',
                    'southwest',
                    'southeast'
                );

                $moves = $this->add_slide_and_slidecapture_moves_to_moves_list($directions_list, 7, $moves, $piece, $color_to_move, $board);
            } elseif ( $piece->type == 'king' ) {
                $directions_list = array(
                    'north',
                    'south',
                    'east',
                    'west',
                    'northwest',
                    'northeast',
                    'southwest',
                    'southeast'
                );
                $moves = $this->add_slide_and_slidecapture_moves_to_moves_list($directions_list, 1, $moves, $piece, $color_to_move, $board);

                // Set $king here so castling function can use it later.
                $king = $piece;
            }
        }

        if ( $moves === array() ) {
            $moves = NULL;
        }

        if ( $eliminate_king_in_check_moves ) {
            $enemy_color = $this->invert_color($color_to_move);

            // Eliminate king in check moves
            $new_moves = array();
            if ( ! $king ) {
                throw new Exception('ChessGame Class - Invalid FEN - One of the kings is missing');
            }
            foreach ( $moves as $key => $move ) {
                $friendly_king_square = $move->board->get_king_square($color_to_move);

                $squares_attacked_by_enemy = $this->get_squares_attacked_by_this_color($enemy_color, $move->board);

                if ( ! in_array($friendly_king_square->alphanumeric, $squares_attacked_by_enemy) ) {
                    array_push($new_moves, $move);
                }
            }

            $moves = $new_moves;

            // Move notation - disambiguate vague starting squares
            // foreach $moves as $key => $move
                // if $move->piece->type == queen, rook, knight, bishop
                    // make list of destination squares
                    // identify duplicates
                    // foreach duplicates as $key => $move2
                        // if column disambiguate this piece
                            // $move->set_disambiguation(column);
                        // elseif row disambiguates this piece
                            // $move->set_disambiguation(row);
                        // else
                            // $move->set_disambiguation(columnrow);

            // Castling
            // (castling does its own "king in check" checks so we can put this code after the "king in check" code)
            $squares_attacked_by_enemy = $this->get_squares_attacked_by_this_color($enemy_color, $board);
            $moves = $this->add_castling_moves_to_moves_list($moves, $king, $squares_attacked_by_enemy, $board);

            // if move puts enemy king in check, tell the $move object so it can add a + to the notation
            foreach ( $moves as $key => $move ) {
                $enemy_king_square = $move->board->get_king_square($enemy_color);

                $squares_attacked_by_moving_side = $this->get_squares_attacked_by_this_color($color_to_move, $move->board);

                if ( in_array($enemy_king_square->alphanumeric, $squares_attacked_by_moving_side) ) {
                    $move->set_enemy_king_in_check(TRUE);
                }
            }

            // alphabetize
        }

        return $moves;
    }

    function add_slide_and_slidecapture_moves_to_moves_list($directions_list, $spaces, $moves, $piece, $color_to_move, $board) {
        foreach ( $directions_list as $key => $direction ) {
            // $spaces should be 1 for king, 1 or 2 for pawns, 7 for all other sliding pieces
            // 7 is the max # of squares you can slide on a chessboard

            $xy = array(
                'north' => array(0,1),
                'south' => array(0,-1),
                'east' => array(1,0),
                'west' => array(-1,0),
                'northeast' => array(1,1),
                'northwest' => array(-1,1),
                'southeast' => array(1,-1),
                'southwest' => array(-1,-1)
            );

            // XY coordinates and rank/file are different. Need to convert.
            $xy = $this->convert_from_xy_to_rankfile($xy);

            $legal_move_list = array();

            for ( $i = 1; $i <= $spaces; $i++ ) {
                $current_xy = $xy[$direction];
                $current_xy[0] *= $i;
                $current_xy[1] *= $i;

                $ending_square = $this->square_exists_and_not_occupied_by_friendly_piece(
                    $piece->square,
                    $current_xy[0],
                    $current_xy[1],
                    $color_to_move,
                    $board
                );

                if ( $ending_square ) {
                    $capture = FALSE;

                    if ( is_a($board->board[$ending_square->rank][$ending_square->file], 'ChessPiece') ) {
                        if ( $board->board[$ending_square->rank][$ending_square->file]->color != $color_to_move ) {
                            $capture = TRUE;
                        }
                    }

                    array_push($legal_move_list, new ChessMove(
                        $piece->square,
                        $ending_square,
                        $piece->color,
                        $piece->type,
                        $capture,
                        $board
                    ));

                    if ( $capture ) {
                        // stop sliding
                        break;
                    } else {
                        // empty square
                        // continue sliding
                        continue;
                    }
                } else {
                    // square does not exist, or square occupied by friendly piece
                    // stop sliding
                    break;
                }
            }

            if ( $legal_move_list === array() ) {
                $legal_move_list = NULL;
            }

            if ( $legal_move_list ) {
                foreach ( $legal_move_list as $key2 => $value2 ) {
                    array_push($moves, $value2);
                }
            }
        }

        return $moves;
    }

    function add_capture_moves_to_moves_list($directions_list, $moves, $piece, $color_to_move, $board) {
        foreach ( $directions_list as $key => $direction ) {
            $xy = array(
                'north' => array(0,1),
                'south' => array(0,-1),
                'east' => array(1,0),
                'west' => array(-1,0),
                'northeast' => array(1,1),
                'northwest' => array(-1,1),
                'southeast' => array(1,-1),
                'southwest' => array(-1,-1)
            );

            // XY coordinates and rank/file are different. Need to convert.
            $xy = $this->convert_from_xy_to_rankfile($xy);

            $legal_move_list = array();

            $current_xy = $xy[$direction];

            $ending_square = $this->square_exists_and_not_occupied_by_friendly_piece(
                $piece->square,
                $current_xy[0],
                $current_xy[1],
                $color_to_move,
                $board
            );

            if ( $ending_square ) {
                $capture = FALSE;

                if ( is_a($board->board[$ending_square->rank][$ending_square->file], 'ChessPiece') ) {
                    if ( $board->board[$ending_square->rank][$ending_square->file]->color != $color_to_move ) {
                        $capture = TRUE;
                    }
                }

                if ( $capture ) {
                    $move = new ChessMove(
                        $piece->square,
                        $ending_square,
                        $piece->color,
                        $piece->type,
                        $capture,
                        $board
                    );

                    // pawn promotion
                    $white_pawn_capturing_on_rank_8 = $piece->type == "pawn" && $ending_square->rank == 8 && $piece->color == "white";
                    $black_pawn_capturing_on_rank_1 = $piece->type == "pawn" && $ending_square->rank == 1 && $piece->color == "black";
                    if (
                        $white_pawn_capturing_on_rank_8 || $black_pawn_capturing_on_rank_1
                    ) {
                        $promotion_pieces = array(
                            'queen',
                            'rook',
                            'bishop',
                            'knight'
                        );

                        foreach ( $promotion_pieces as $key => $type ) {
                            $move2 = clone $move;
                            $move2->set_promotion_piece($type);
                            array_push($legal_move_list, $move2);
                        }
                    } else {
                        array_push($legal_move_list, $move);
                    }
                }
            }

            if ( $legal_move_list === array() ) {
                $legal_move_list = NULL;
            }

            if ( $legal_move_list ) {
                foreach ( $legal_move_list as $key2 => $value2 ) {
                    array_push($moves, $value2);
                }
            }
        }

        return $moves;
    }

    function add_slide_moves_to_moves_list($directions_list, $spaces, $moves, $piece, $color_to_move, $board) {
        foreach ( $directions_list as $key => $direction ) {
            // $spaces should be 1 for king, 1 or 2 for pawns, 7 for all other sliding pieces
            // 7 is the max # of squares you can slide on a chessboard

            $xy = array(
                'north' => array(0,1),
                'south' => array(0,-1),
                'east' => array(1,0),
                'west' => array(-1,0),
                'northeast' => array(1,1),
                'northwest' => array(-1,1),
                'southeast' => array(1,-1),
                'southwest' => array(-1,-1)
            );

            // XY coordinates and rank/file are different. Need to convert.
            $xy = $this->convert_from_xy_to_rankfile($xy);

            $legal_move_list = array();

            for ( $i = 1; $i <= $spaces; $i++ ) {
                $current_xy = $xy[$direction];
                $current_xy[0] *= $i;
                $current_xy[1] *= $i;

                $ending_square = $this->square_exists_and_not_occupied_by_friendly_piece(
                    $piece->square,
                    $current_xy[0],
                    $current_xy[1],
                    $color_to_move,
                    $board
                );

                if ( $ending_square ) {
                    $capture = FALSE;

                    if ( is_a($board->board[$ending_square->rank][$ending_square->file], 'ChessPiece') ) {
                        if ( $board->board[$ending_square->rank][$ending_square->file]->color != $color_to_move ) {
                            $capture = TRUE;
                        }
                    }

                    if ( $capture ) {
                        // enemy piece in square
                        // stop sliding
                        break;
                    } else {
                        $new_move = new ChessMove(
                            $piece->square,
                            $ending_square,
                            $piece->color,
                            $piece->type,
                            $capture,
                            $board
                        );

                        // en passant target square
                        if (
                            $piece->type == 'pawn' &&
                            $i == 2
                        ) {
                            $en_passant_xy = $xy[$direction];
                            $en_passant_xy[0] *= 1;
                            $en_passant_xy[1] *= 1;

                            $en_passant_target_square = $this->square_exists_and_not_occupied_by_friendly_piece(
                                $piece->square,
                                $en_passant_xy[0],
                                $en_passant_xy[1],
                                $color_to_move,
                                $board
                            );

                            $new_move->board->set_en_passant_target_square($en_passant_target_square);
                        }

                        // pawn promotion
                        $white_pawn_moving_to_rank_8 = $piece->type == "pawn" && $ending_square->rank == 8 && $piece->color == "white";
                        $black_pawn_moving_to_rank_1 = $piece->type == "pawn" && $ending_square->rank == 1 && $piece->color == "black";
                        if (
                            $white_pawn_moving_to_rank_8 || $black_pawn_moving_to_rank_1
                        ) {
                            $promotion_pieces = array(
                                'queen',
                                'rook',
                                'bishop',
                                'knight'
                            );

                            foreach ( $promotion_pieces as $key => $type ) {
                                $move2 = clone $new_move;
                                $move2->set_promotion_piece($type);
                                array_push($legal_move_list, $move2);
                            }
                        } else {
                            array_push($legal_move_list, $new_move);
                        }

                        // empty square
                        // continue sliding
                        continue;
                    }
                } else {
                    // square does not exist, or square occupied by friendly piece
                    // stop sliding
                    break;
                }
            }

            if ( $legal_move_list === array() ) {
                $legal_move_list = NULL;
            }

            if ( $legal_move_list ) {
                foreach ( $legal_move_list as $key2 => $value2 ) {
                    array_push($moves, $value2);
                }
            }
        }

        return $moves;
    }

    function add_jump_and_jumpcapture_moves_to_moves_list($oclock_list, $moves, $piece, $color_to_move, $board) {
        foreach ( $oclock_list as $key => $oclock ) {
            $xy = array(
                1 => array(1,2),
                2 => array(2,1),
                4 => array(2,-1),
                5 => array(1,-2),
                7 => array(-1,-2),
                8 => array(-2,-1),
                10 => array(-2,1),
                11 => array(-1,2)
            );

            // XY coordinates and rank/file are different. Need to convert.
            $xy = $this->convert_from_xy_to_rankfile($xy);

            $ending_square = $this->square_exists_and_not_occupied_by_friendly_piece(
                $piece->square,
                $xy[$oclock][0],
                $xy[$oclock][1],
                $color_to_move,
                $board
            );

            $legal_move_list = array();

            if ( $ending_square ) {
                $capture = FALSE;

                if ( is_a($board->board[$ending_square->rank][$ending_square->file], 'ChessPiece') ) {
                    // enemy piece
                    if ( $board->board[$ending_square->rank][$ending_square->file]->color != $color_to_move ) {
                        $capture = TRUE;
                    }
                }

                array_push($legal_move_list, new ChessMove(
                    $piece->square,
                    $ending_square,
                    $piece->color,
                    $piece->type,
                    $capture,
                    $board
                ));
            }

            if ( $legal_move_list === array() ) {
                $legal_move_list = NULL;
            }

            if ( $legal_move_list ) {
                foreach ( $legal_move_list as $key2 => $value2 ) {
                    array_push($moves, $value2);
                }
            }
        }

        return $moves;
    }

    function add_castling_moves_to_moves_list($moves, $piece, $squares_attacked_by_enemy, $board) {
        $scenarios = array (
            array(
                'boolean_to_check' => 'white_can_castle_kingside',
                'color_to_move' => 'white',
                'rook_start_square' => new ChessSquare('h1'),
                'king_end_square' => new ChessSquare('g1'),
                'cannot_be_attacked' => array(
                    new ChessSquare('e1'),
                    new ChessSquare('f1'),
                    new ChessSquare('g1')
                ),
                'cannot_be_occupied' => array(
                    new ChessSquare('f1'),
                    new ChessSquare('g1')
                )
            ),
            array(
                'boolean_to_check' => 'white_can_castle_queenside',
                'color_to_move' => 'white',
                'rook_start_square' => new ChessSquare('a1'),
                'king_end_square' => new ChessSquare('c1'),
                'cannot_be_attacked' => array(
                    new ChessSquare('e1'),
                    new ChessSquare('d1'),
                    new ChessSquare('c1')
                ),
                'cannot_be_occupied' => array(
                    new ChessSquare('d1'),
                    new ChessSquare('c1'),
                    new ChessSquare('b1')
                )
            ),
            array(
                'boolean_to_check' => 'black_can_castle_kingside',
                'color_to_move' => 'black',
                'rook_start_square' => new ChessSquare('h8'),
                'king_end_square' => new ChessSquare('g8'),
                'cannot_be_attacked' => array(
                    new ChessSquare('e8'),
                    new ChessSquare('f8'),
                    new ChessSquare('g8')
                ),
                'cannot_be_occupied' => array(
                    new ChessSquare('f8'),
                    new ChessSquare('g8')
                )
            ),
            array(
                'boolean_to_check' => 'black_can_castle_queenside',
                'color_to_move' => 'black',
                'rook_start_square' => new ChessSquare('a8'),
                'king_end_square' => new ChessSquare('c8'),
                'cannot_be_attacked' => array(
                    new ChessSquare('e8'),
                    new ChessSquare('d8'),
                    new ChessSquare('c8')
                ),
                'cannot_be_occupied' => array(
                    new ChessSquare('d8'),
                    new ChessSquare('c8'),
                    new ChessSquare('b8')
                )
            ),
        );

        $legal_move_list = array();

        foreach ( $scenarios as $key => $value ) {
            // only check castling for current color_to_move
            if ( $value['color_to_move'] != $board->color_to_move ) {
                continue;
            }

            // make sure the FEN has castling permissions
            $boolean_to_check = $value['boolean_to_check'];
            if ( ! $board->castling[$boolean_to_check] ) {
                continue;
            }

            // check all cannot_be_attacked squares
            foreach ( $value['cannot_be_attacked'] as $key2 => $square_to_check ) {
                if ( in_array($square_to_check->alphanumeric, $squares_attacked_by_enemy) ) {
                    continue 2;
                }
            }

            // check all cannot_be_occupied_squares
            foreach ( $value['cannot_be_occupied'] as $key2 => $square_to_check ) {
                if ( $board->square_is_occupied($square_to_check) ) {
                    continue 2;
                }
            }

            // Make sure the rook is still there. This case should only occur in damaged FENs. If the rook isn't there, throw an invalid FEN exception (to prevent a clone error later on).
            $rook_start_square = $value['rook_start_square'];
            $rank = $rook_start_square->rank;
            $file = $rook_start_square->file;
            $piece_to_check = $board->board[$rank][$file];
            if ( ! $piece_to_check ) {
                throw new Exception('ChessGame Class - Invalid FEN - Castling permissions set to TRUE but rook is missing');
            }
            if (
                $piece_to_check->type != 'rook' ||
                $piece_to_check->color != $board->color_to_move
            ) {
                throw new Exception('ChessGame Class - Invalid FEN - Castling permissions set to TRUE but rook is missing');
            }

            // The ChessMove class handles displaying castling notation, taking castling privileges out of the FEN, and moving the rook into the right place on the board. No need to do anything extra here.
            array_push($legal_move_list, new ChessMove(
                $piece->square,
                $value['king_end_square'],
                $piece->color,
                $piece->type,
                FALSE,
                $board
            ));
        }

        if ( $legal_move_list === array() ) {
            $legal_move_list = NULL;
        }

        if ( $legal_move_list ) {
            foreach ( $legal_move_list as $key2 => $value2 ) {
                array_push($moves, $value2);
            }
        }

        return $moves;
    }

    function add_en_passant_moves_to_moves_list($piece, $board, $moves) {
        if ( $piece->color == 'white' ) {
            $capture_directions_from_starting_square = array('northeast', 'northwest');
            $enemy_pawn_direction_from_ending_square = array('south');
            $en_passant_rank = 5;
        } elseif ( $piece->color == 'black' ) {
            $capture_directions_from_starting_square = array('southeast', 'southwest');
            $enemy_pawn_direction_from_ending_square = array('north');
            $en_passant_rank = 4;
        }

        if ( $piece->on_rank($en_passant_rank) && $board->en_passant_target_square ) {
            $squares_to_check = $this->get_squares_in_these_directions($piece->square, $capture_directions_from_starting_square, 1);
            foreach ( $squares_to_check as $key => $square ) {
                if ( $square->alphanumeric == $board->en_passant_target_square->alphanumeric ) {
                    $move = new ChessMove(
                        $piece->square,
                        $square,
                        $piece->color,
                        $piece->type,
                        TRUE,
                        $board
                    );
                    $move->set_en_passant(TRUE);
                    $enemy_pawn_square = $this->get_squares_in_these_directions($square, $enemy_pawn_direction_from_ending_square, 1);
                    $move->board->remove_piece_from_square($enemy_pawn_square[0]);
                    array_push($moves, $move);
                }
            }
        }

        return $moves;
    }

    function convert_from_xy_to_rankfile($xy) {
        // XY coordinates and rank/file are different. Need to convert.
        // We basically need to flip X and Y to fix it.

        foreach ( $xy as $key => $value ) {
            $xy[$key] = array($value[1], $value[0]);
        }

        return $xy;
    }

    function get_all_pieces_by_color($color_to_move, $board) {
        $list_of_pieces = array();

        for ( $i = 1; $i <= 8; $i++ ) {
            for ( $j = 1; $j <=8; $j++ ) {
                $piece = $board->board[$i][$j];

                if ( $piece ) {
                    if ( $piece->color == $color_to_move ) {
                        array_push($list_of_pieces, $piece);
                    }
                }
            }
        }

        if ( $list_of_pieces === array() ) {
            $list_of_pieces = NULL;
        }

        return $list_of_pieces;
    }

    // positive X = east, negative X = west, positive Y = north, negative Y = south
    function square_exists_and_not_occupied_by_friendly_piece($starting_square, $x_delta, $y_delta, $color_to_move, $board) {
        $rank = $starting_square->rank + $x_delta;
        $file = $starting_square->file + $y_delta;

        $ending_square = $this->try_to_make_square_using_rank_and_file_num($rank, $file);

        // Ending square is off the board
        if ( ! $ending_square ) {
            return FALSE;
        }

        // Ending square contains a friendly piece
        if ( is_a($board->board[$rank][$file], 'ChessPiece') ) {
            if ( $board->board[$rank][$file]->color == $color_to_move ) {
                return FALSE;
            }
        }

        return $ending_square;
    }

    function try_to_make_square_using_rank_and_file_num($rank, $file) {
        $file_letters = new Dictionary(array(
            1 => 'a',
            2 => 'b',
            3 => 'c',
            4 => 'd',
            5 => 'e',
            6 => 'f',
            7 => 'g',
            8 => 'h'
        ));

        $alphanumeric = $file_letters->check_dictionary($file) . $rank;

        $valid_squares = array(
            'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8',
            'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8',
            'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8',
            'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8',
            'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8',
            'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8',
            'g1', 'g2', 'g3', 'g4', 'g5', 'g6', 'g7', 'g8',
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8'
        );


        if ( in_array($alphanumeric, $valid_squares) ) {
            return new ChessSquare($alphanumeric);
        } else {
            return FALSE;
        }
    }

    function invert_color($color) {
        if ( $color == 'white' ) {
            return 'black';
        } else {
            return 'white';
        }
    }

    function get_squares_attacked_by_this_color($color, $board) {
        $legal_moves_for_opponent = $this->get_legal_moves_list($color, $board, FALSE);

        $squares_attacked = array();
        foreach ( $legal_moves_for_opponent as $key => $move ) {
            // avoid duplicates
            if ( ! in_array($move->ending_square->alphanumeric, $squares_attacked) ) {
                array_push($squares_attacked, $move->ending_square->alphanumeric);
            }
        }

        return $squares_attacked;
    }

    // Used to generate en passant squares.
    function get_squares_in_these_directions($starting_square, $directions_list, $spaces) {
        $list_of_squares = array();

        foreach ( $directions_list as $key => $direction ) {
            // $spaces should be 1 for king, 1 or 2 for pawns, 7 for all other sliding pieces
            // 7 is the max # of squares you can slide on a chessboard

            $xy = array(
                'north' => array(0,1),
                'south' => array(0,-1),
                'east' => array(1,0),
                'west' => array(-1,0),
                'northeast' => array(1,1),
                'northwest' => array(-1,1),
                'southeast' => array(1,-1),
                'southwest' => array(-1,-1)
            );

            // XY coordinates and rank/file are different. Need to convert.
            $xy = $this->convert_from_xy_to_rankfile($xy);

            $current_xy = $xy[$direction];
            $current_xy[0] =  $current_xy[0] * $spaces + $starting_square->rank;
            $current_xy[1] =  $current_xy[1] * $spaces + $starting_square->file;

            $square = $this->try_to_make_square_using_rank_and_file_num($current_xy[0], $current_xy[1]);

            if ( $square ) {
                array_push($list_of_squares, $square);
            }
        }

        if ( $list_of_squares === array() ) {
            $list_of_squares = NULL;
        }

        return $list_of_squares;
    }}

ChessMove.php

<?php

class ChessMove {
    const PIECE_LETTERS = array(
        'p' => 'pawn',
        'n' => 'knight',
        'b' => 'bishop',
        'r' => 'rook',
        'q' => 'queen',
        'k' => 'king'
    );

    var $starting_square;
    var $ending_square;
    var $color;
    var $piece_type;
    var $capture;
    var $check;
    var $checkmate;
    var $promotion_piece_type;
    var $en_passant_capture;
    var $disambiguation;

    var $notation;
    var $coordiante_notation;

    var $board;

    function __construct(
        $starting_square,
        $ending_square,
        $color,
        $piece_type,
        $capture,
        $old_board
    ) {
        $this->starting_square = $starting_square;
        $this->ending_square = $ending_square;
        $this->color = $color;
        $this->piece_type = $piece_type;
        $this->capture = $capture;

        // These cases are rare. The data is passed in via set functions instead of in the constructor.
        $this->disambiguation = '';
        $this->promotion_piece_type = NULL;
        $this->en_passant = FALSE;
        $this->check = FALSE;
        $this->checkmate = FALSE;

        $this->notation = $this->get_notation();
        $this->coordinate_notation = $this->starting_square->alphanumeric . $this->ending_square->alphanumeric;

        $this->board = clone $old_board;
        $this->board->make_move($starting_square, $ending_square);

        // if the king or rook moves, update the FEN to take away castling privileges
        if ( $this->color == 'black' ) {
            if (
                $this->piece_type == 'king' &&
                $this->starting_square->alphanumeric == 'e8'
            ) {
                $this->board->set_castling('black_can_castle_kingside', FALSE);
                $this->board->set_castling('black_can_castle_queenside', FALSE);
            } elseif (
                $this->piece_type == 'rook' &&
                $this->starting_square->alphanumeric == 'a8'
            ) {
                $this->board->set_castling('black_can_castle_queenside', FALSE);
            } elseif (
                $this->piece_type == 'rook' &&
                $this->starting_square->alphanumeric == 'h8'
            ) {
                $this->board->set_castling('black_can_castle_kingside', FALSE);
            }
        } elseif ( $this->color == 'white' ) {
            if (
                $this->piece_type == 'king' &&
                $this->starting_square->alphanumeric == 'e1'
            ) {
                $this->board->set_castling('white_can_castle_kingside', FALSE);
                $this->board->set_castling('white_can_castle_queenside', FALSE);
            } elseif (
                $this->piece_type == 'rook' &&
                $this->starting_square->alphanumeric == 'a1'
            ) {
                $this->board->set_castling('white_can_castle_queenside', FALSE);
            } elseif (
                $this->piece_type == 'rook' &&
                $this->starting_square->alphanumeric == 'h1'
            ) {
                $this->board->set_castling('white_can_castle_kingside', FALSE);
            }
        }

        // if castling, move the rook into the right place
        if ( $this->color == 'black' ) {
            if (
                $this->piece_type == 'king' &&
                $this->starting_square->alphanumeric == 'e8' &&
                $this->ending_square->alphanumeric == 'g8'
            ) {
                $starting_square = new ChessSquare('h8');
                $ending_square = new ChessSquare('f8');
                $this->board->make_additional_move_on_same_turn($starting_square, $ending_square);
            } elseif (
                $this->piece_type == 'king' &&
                $this->starting_square->alphanumeric == 'e8' &&
                $this->ending_square->alphanumeric == 'c8'
            ) {
                $starting_square = new ChessSquare('a8');
                $ending_square = new ChessSquare('d8');
                $this->board->make_additional_move_on_same_turn($starting_square, $ending_square);
            }
        } elseif ( $this->color == 'white' ) {
            if (
                $this->piece_type == 'king' &&
                $this->starting_square->alphanumeric == 'e1' &&
                $this->ending_square->alphanumeric == 'g1'
            ) {
                $starting_square = new ChessSquare('h1');
                $ending_square = new ChessSquare('f1');
                $this->board->make_additional_move_on_same_turn($starting_square, $ending_square);
            } elseif (
                $this->piece_type == 'king' &&
                $this->starting_square->alphanumeric == 'e1' &&
                $this->ending_square->alphanumeric == 'c1'
            ) {
                $starting_square = new ChessSquare('a1');
                $ending_square = new ChessSquare('d1');
                $this->board->make_additional_move_on_same_turn($starting_square, $ending_square);
            }
        }
    }

    // Do a deep clone. Needed for pawn promotion.
    function __clone() {
        $this->starting_square = clone $this->starting_square;
        $this->ending_square = clone $this->ending_square;
        $this->board = clone $this->board;
    }

    function set_promotion_piece($piece_type) {
        // update the piece
        $rank = $this->ending_square->rank;
        $file = $this->ending_square->file;
        $this->board->board[$rank][$file]->type = $piece_type;
        $this->board->update_fen();

        // Automatically pick queen when drag and dropping.
        if ( $piece_type != "queen" ) {
            $this->coordinate_notation = "";
        }

        // update the notation
        $this->promotion_piece_type = $piece_type;
        $this->notation = $this->get_notation();
    }

    function set_enemy_king_in_check($boolean) {
        $this->check = $boolean;

        $this->notation = $this->get_notation();
    }

    function set_checkmate($boolean) {
        $this->checkmate = $boolean;

        $this->notation = $this->get_notation();
    }

    function set_en_passant($boolean) {
        $this->en_passant = $boolean;

        $this->notation = $this->get_notation();
    }

    function set_disambiguation($string) {
        $this->disambiguation = $string;

        $this->notation = $this->get_notation();
    }

    function get_notation() {
        $string = '';

        if (
            $this->starting_square->alphanumeric == 'e8' &&
            $this->ending_square->alphanumeric == 'g8' &&
            $this->piece_type == 'king' &&
            $this->color = 'black'
        ) {
            $string .= 'O-O';
        } elseif (
            $this->starting_square->alphanumeric == 'e1' &&
            $this->ending_square->alphanumeric == 'g1' &&
            $this->piece_type == 'king' &&
            $this->color = 'white'
        ) {
            $string .= 'O-O';
        } elseif (
            $this->starting_square->alphanumeric == 'e8' &&
            $this->ending_square->alphanumeric == 'c8' &&
            $this->piece_type == 'king' &&
            $this->color = 'black'
        ) {
            $string .= 'O-O-O';
        } elseif (
            $this->starting_square->alphanumeric == 'e1' &&
            $this->ending_square->alphanumeric == 'c1' &&
            $this->piece_type == 'king' &&
            $this->color = 'white'
        ) {
            $string .= 'O-O-O';
        } else {
            // type of piece
            if ( $this->piece_type == 'pawn' && $this->capture ) {
                $string .= substr($this->starting_square->alphanumeric, 0, 1);
            } elseif ( $this->piece_type != 'pawn' ) {
                $string .= strtoupper(array_search(
                    $this->piece_type,
                    self::PIECE_LETTERS
                ));
            }

            // disambiguation rank/file/square
            $string .= $this->disambiguation;

            // capture?
            if ( $this->capture ) {
                $string .= 'x';
            }

            // destination square
            $string .= $this->ending_square->alphanumeric;

            // en passant
            if ( $this->en_passant ) {
                $string .= 'e.p.';
            }

            // pawn promotion
            if ( $this->promotion_piece_type == 'queen' ) {
                $string .= '=Q';
            } elseif ( $this->promotion_piece_type == 'rook' ) {
                $string .= '=R';
            } elseif ( $this->promotion_piece_type == 'bishop' ) {
                $string .= '=B';
            } elseif ( $this->promotion_piece_type == 'knight' ) {
                $string .= '=N';
            }
        }

        // check or checkmate
        if ( $this->checkmate ) {
            $string .= '#';
        } elseif ( $this->check ) {
            $string .= '+';
        }

        return $string;
    }
}

ChessBoard.php

<?php

class ChessBoard {
    const PIECE_LETTERS = array(
        'p' => 'pawn',
        'n' => 'knight',
        'b' => 'bishop',
        'r' => 'rook',
        'q' => 'queen',
        'k' => 'king'
    );

    var $board = array(); // $board[y][x], or in this case, $board[rank][file]
    var $color_to_move;
    var $castling = array(); // format is array('white_can_castle_kingside' => TRUE, etc.)
    var $en_passant_target_square = NULL;
    var $halfmove_clock;
    var $fullmove_number;

    var $fen;

    function __construct($fen) {
        $this->set_fen($fen);
        $this->fen = $fen;
    }

    function __clone() {
        if ( $this->board ) {
            for ( $rank = 1; $rank <= 8; $rank++ ) {
                for ( $file = 1; $file <= 8; $file++ ) {
                    if ( $this->board[$rank][$file] ) {
                        $this->board[$rank][$file] = clone $this->board[$rank][$file];
                    }
                }
            }
        }
    }

    function set_fen($fen) {
        $fen = trim($fen);

        // set everything back to default
        $legal_moves = array();
        $checkmate = FALSE;
        $stalemate = FALSE;
        $move_list = array();
        // TODO: add more

        // Basic format check. This won't catch everything, but it will catch a lot of stuff.
        // TODO: Make this stricter so that it catches everything.
        $valid_fen = preg_match('/^([rnbqkpRNBQKP12345678]{1,8})/([rnbqkpRNBQKP12345678]{1,8})/([rnbqkpRNBQKP12345678]{1,8})/([rnbqkpRNBQKP12345678]{1,8})/([rnbqkpRNBQKP12345678]{1,8})/([rnbqkpRNBQKP12345678]{1,8})/([rnbqkpRNBQKP12345678]{1,8})/([rnbqkpRNBQKP12345678]{1,8}) ([bw]{1}) ([-KQkq]{1,4}) ([a-h1-8-]{1,2}) (d{1,2}) (d{1,4})$/', $fen, $matches);

        if ( ! $valid_fen ) {
            throw new Exception('ChessBoard Class - Invalid FEN');
        }

        // ******* CREATE PIECES AND ASSIGN THEM TO SQUARES *******

        // Set all board squares to NULL. That way we don't have to blank them in the loop below. We can just overwrite the NULL with a piece.
        for ( $i = 1; $i <= 8; $i++ ) {
            for ( $j = 1; $j <= 8; $j++ ) {
                $this->board[$i][$j] = NULL;
            }
        }

        // Create $rank variables with strings that look like this
            // rnbqkbnr
            // pppppppp
            // 8
            // PPPPPPPP
            // RNBQKBNR
            // 2p5
        // The numbers are the # of blank squares from left to right
        $rank = array();
        for ( $i = 1; $i <= 8; $i++ ) {
            // Match string = 1, but rank = 8. Fix it here to avoid headaches.
            $rank = $this->invert_rank_or_file_number($i);
            $rank_string[$rank] = $matches[$i];
        }

        // Process $rank variable strings, convert to pieces and add them to $this->board[][]
        foreach ( $rank_string as $rank => $string ) {
            $file = 1;

            for ( $i = 1; $i <= strlen($string); $i++ ) {
                $char = substr($string, $i - 1, 1);

                // Don't use is_int here. $char is a string. Use is_numeric instead.
                if ( is_numeric($char) ) {
                    $file = $file + $char;
                } else {
                    $square = $this->number_to_file($file) . $rank;

                    if ( ctype_upper($char) ) {
                        $color = 'white';
                    } else {
                        $color = 'black';
                    }

                    $type = self::PIECE_LETTERS[strtolower($char)];

                    $this->board[$rank][$file] = new ChessPiece($color, $square, $type);

                    $file++;
                }
            }
        }

        // ******* SET COLOR TO MOVE *******
        if ( $matches[9] == 'w' ) {
            $this->color_to_move = 'white';
        } elseif ( $matches[9] == 'b' ) {
            $this->color_to_move = 'black';
        } else {
            throw new Exception('ChessBoard Class - Invalid FEN - Invalid Color To Move');
        }

        // Set all castling to false. Only set to true if letter is present in FEN. Prevents bugs.
        $this->castling['white_can_castle_kingside'] = FALSE;
        $this->castling['white_can_castle_queenside'] = FALSE;
        $this->castling['black_can_castle_kingside'] = FALSE;
        $this->castling['black_can_castle_queenside'] = FALSE;

        // ******* SET CASTLING POSSIBILITIES *******
        // strpos is case sensitive, so that's good
        if ( strpos($matches[10], 'K') !== FALSE ) {
            $this->castling['white_can_castle_kingside'] = TRUE;
        }

        if ( strpos($matches[10], 'Q') !== FALSE ) {
            $this->castling['white_can_castle_queenside'] = TRUE;
        }

        if ( strpos($matches[10], 'k') !== FALSE ) {
            $this->castling['black_can_castle_kingside'] = TRUE;
        }

        if ( strpos($matches[10], 'q') !== FALSE ) {
            $this->castling['black_can_castle_queenside'] = TRUE;
        }

        // ******* SET EN PASSANT TARGET SQUARE *******
        if ( $matches[11] == '-' ) {
            $this->en_passant_target_square = FALSE;
        } else {
            $this->en_passant_target_square = new ChessSquare($matches[11]);
        }
        // ChessPiece throws its own exceptions, so no need to throw one here.

        // ******* SET HALFMOVE CLOCK *******
        $this->halfmove_clock = $matches[12];

        // ******* SET FULLMOVE NUMBER *******
        $this->fullmove_number = $matches[13];

        // ******* SET HALFMOVE NUMBER *******
        $this->halfmove_number = $matches[13] * 2 - 1;
        if ( $this->color_to_move == 'black' ) {
            $this->halfmove_number++;
        }

        $this->fen = $fen;
    }

    function get_fen() {
        $string = '';

        // A chessboard looks like this
            // a8 b8 c8 d8
            // a7 b7 c7 d7
            // etc.
        // But we want to print them starting with row 8 first.
        // So we need to adjust the loops a bit.

        for ( $rank = 8; $rank >= 1; $rank-- ) {
            $empty_squares = 0;

            for ( $file = 1; $file <= 8; $file++ ) {
                $piece = $this->board[$rank][$file];

                if ( ! $piece ) {
                    $empty_squares++;
                } else {
                    if ( $empty_squares ) {
                        $string .= $empty_squares;
                        $empty_squares = 0;
                    }
                    $string .= $piece->get_fen_symbol();
                }
            }

            if ( $empty_squares ) {
                $string .= $empty_squares;
            }

            if ( $rank != 1 ) {
                $string .= "/";
            }
        }

        if ( $this->color_to_move == 'white' ) {
            $string .= " w ";
        } elseif ( $this->color_to_move == 'black' ) {
            $string .= " b ";
        }

        if ( $this->castling['white_can_castle_kingside'] ) {
            $string .= "K";
        }

        if ( $this->castling['white_can_castle_queenside'] ) {
            $string .= "Q";
        }

        if ( $this->castling['black_can_castle_kingside'] ) {
            $string .= "k";
        }

        if ( $this->castling['black_can_castle_queenside'] ) {
            $string .= "q";
        }

        if (
            ! $this->castling['white_can_castle_kingside'] &&
            ! $this->castling['white_can_castle_queenside'] &&
            ! $this->castling['black_can_castle_kingside'] &&
            ! $this->castling['black_can_castle_queenside']
        ) {
            $string .= "-";
        }

        if ( $this->en_passant_target_square ) {
            $string .= " " . $this->en_passant_target_square->alphanumeric;
        } else {
            $string .= " -";
        }

        $string .= " " . $this->halfmove_clock . ' ' . $this->fullmove_number;

        return $string;
    }

    function update_fen() {
        $this->fen = $this->get_fen();
    }

    // Keeping this for debug reasons.
    function get_ascii_board() {
        $string = '';

        if ( $this->color_to_move == 'white' ) {
            $string .= "White To Move";
        } elseif ( $this->color_to_move == 'black' ) {
            $string .= "Black To Move";
        }

        // A chessboard looks like this
            // a8 b8 c8 d8
            // a7 b7 c7 d7
            // etc.
        // But we want to print them starting with row 8 first.
        // So we need to adjust the loops a bit.

        for ( $rank = 8; $rank >= 1; $rank-- ) {
            $string .= "<br />";

            for ( $file = 1; $file <= 8; $file++ ) {
                $square = $this->board[$rank][$file];

                if ( ! $square ) {
                    $string .= "*";
                } else {
                    $string .= $this->board[$rank][$file]->get_unicode_symbol();
                }
            }
        }
        $string .= "<br /><br />";

        return $string;
    }

    function get_graphical_board() {
        // We need to throw some variables into an array so our view can build the board.
        // The array shall be in the following format:
            // square_color = black / white
            // id = a1-h8
            // piece = HTML unicode for that piece

        // A chessboard looks like this
            // a8 b8 c8 d8
            // a7 b7 c7 d7
            // etc.
        // But we want to print them starting with row 8 first.
        // So we need to adjust the loops a bit.

        $graphical_board_array = array();
        for ( $rank = 8; $rank >= 1; $rank-- ) {
            for ( $file = 1; $file <= 8; $file++ ) {
                $piece = $this->board[$rank][$file];

                // SQUARE COLOR
                if ( ($rank + $file) % 2 == 1 ) {
                    $graphical_board_array[$rank][$file]['square_color'] = 'white';
                } else {
                    $graphical_board_array[$rank][$file]['square_color'] = 'black';
                }

                // ID
                $file_letters = new Dictionary(array(
                    1 => 'a',
                    2 => 'b',
                    3 => 'c',
                    4 => 'd',
                    5 => 'e',
                    6 => 'f',
                    7 => 'g',
                    8 => 'h'
                ));
                $graphical_board_array[$rank][$file]['id'] = $file_letters->check_dictionary($file) . $rank;

                // PIECE
                if ( ! $piece ) {
                    $graphical_board_array[$rank][$file]['piece'] = '';
                } else {
                    $graphical_board_array[$rank][$file]['piece'] = $this->board[$rank][$file]->get_unicode_symbol();
                }
            }
        }

        return $graphical_board_array;
    }

    function get_side_to_move_string() {
        $string = '';

        if ( $this->color_to_move == 'white' ) {
            $string .= "White To Move";
        } elseif ( $this->color_to_move == 'black' ) {
            $string .= "Black To Move";
        }

        return $string;
    }

    function get_who_is_winning_string() {
        $points = 0;

        foreach ( $this->board as $key1 => $value1 ) {
            foreach ( $value1 as $key2 => $piece ) {
                if ( $piece ) {
                    $points += $piece->value;
                }
            }
        }

        if ( $points > 0 ) {
            return "Material: White Ahead By $points";
        } elseif ( $points < 0 ) {
            $points *= -1;
            return "Material: Black Ahead By $points";
        } else {
            return "Material: Equal";
        }
    }

    function invert_rank_or_file_number($number) {
        $dictionary = array(
            1 => 8,
            2 => 7,
            3 => 6,
            4 => 5,
            5 => 4,
            6 => 3,
            7 => 2,
            8 => 1
        );

        return $dictionary[$number];
    }

    function number_to_file($number) {
        $dictionary = array(
            1 => 'a',
            2 => 'b',
            3 => 'c',
            4 => 'd',
            5 => 'e',
            6 => 'f',
            7 => 'g',
            8 => 'h'
        );

        if ( ! array_key_exists($number, $dictionary) ) {
            throw new Exception('ChessBoard Class - number_to_file - unknown file number - $number = ' . var_export($number, TRUE));
        }

        return $dictionary[$number];
    }

    // Note: This does not check for and reject illegal moves. It is up to code in the ChessGame class to generate a list of legal moves, then only make_move those moves.
    // In fact, sometimes make_move will be used on illegal moves (king in check moves), then the illegal moves will be deleted from the list of legal moves in a later step.
    function make_move($old_square, $new_square) {
        $moving_piece = clone $this->board[$old_square->rank][$old_square->file];

        $this->set_en_passant_target_square(NULL);

        $is_capture = $this->board[$new_square->rank][$new_square->file];

        if (
            $moving_piece->type == 'pawn' ||
            $is_capture
        ) {
            $this->halfmove_clock = 0;
        } else {
            $this->halfmove_clock++;
        }

        $this->board[$new_square->rank][$new_square->file] = $moving_piece;

        // Update $moving_piece->square too to avoid errors.
        $moving_piece->square = $new_square;

        $this->board[$old_square->rank][$old_square->file] = NULL;

        if ( $this->color_to_move == 'black' ) {
            $this->fullmove_number++;
        }

        $this->flip_color_to_move();

        $this->update_fen();
    }

    // Used to move the rook during castling.
    // Can't use make_move because it messes up color_to_move, halfmove, and fullmove.
    function make_additional_move_on_same_turn($old_square, $new_square) {
        $moving_piece = clone $this->board[$old_square->rank][$old_square->file];

        $this->board[$new_square->rank][$new_square->file] = $moving_piece;

        // Update $moving_piece->square too to avoid errors.
        $moving_piece->square = $new_square;

        $this->board[$old_square->rank][$old_square->file] = NULL;

        $this->update_fen();
    }

    function flip_color_to_move() {
        if ( $this->color_to_move == 'white' ) {
            $this->color_to_move = 'black';
        } elseif ( $this->color_to_move == 'black' ) {
            $this->color_to_move = 'white';
        }
    }

    function set_castling($string, $boolean) {
        $this->castling[$string] = $boolean;
        $this->update_fen();
    }

    function set_en_passant_target_square($square) {
        $this->en_passant_target_square = $square;
        $this->update_fen();
    }

    function square_is_occupied($square) {
        $rank = $square->rank;
        $file = $square->file;

        if ( $this->board[$rank][$file] ) {
            return TRUE;
        } else {
            return FALSE;
        }
    }

    function get_king_square($color) {
        foreach ( $this->board as $key => $value ) {
            foreach ( $value as $key2 => $piece ) {
                if ( $piece ) {
                    if ( $piece->type == 'king' && $piece->color == $color ) {
                        return $piece->square;
                    }
                }
            }
        }

        return NULL;
    }

    function remove_piece_from_square($square) {
        $rank = $square->rank;
        $file = $square->file;

        $this->board[$rank][$file] = NULL;

        $this->update_fen();
    }
}

ChessPiece.php

<?php

class ChessPiece
{
    var $value;
    var $color;
    var $type;
    var $square;

    const VALID_COLORS = array('white', 'black');
    const VALID_TYPES = array('pawn', 'knight', 'bishop', 'rook', 'queen', 'king');
    const UNICODE_CHESS_PIECES = array(
        'white_king' => '♔',
        'white_queen' => '♕',
        'white_rook' => '♖',
        'white_bishop' => '♗',
        'white_knight' => '♘',
        'white_pawn' => '♙',
        'black_king' => '♚',
        'black_queen' => '♛',
        'black_rook' => '♜',
        'black_bishop' => '♝',
        'black_knight' => '♞',
        'black_pawn' => '♟'
    );
    const FEN_CHESS_PIECES = array(
        'white_king' => 'K',
        'white_queen' => 'Q',
        'white_rook' => 'R',
        'white_bishop' => 'B',
        'white_knight' => 'N',
        'white_pawn' => 'P',
        'black_king' => 'k',
        'black_queen' => 'q',
        'black_rook' => 'r',
        'black_bishop' => 'b',
        'black_knight' => 'n',
        'black_pawn' => 'p'
    );
    const PIECE_VALUES = array(
        'pawn' => 1,
        'knight' => 3,
        'bishop' => 3,
        'rook' => 5,
        'queen' => 9,
        'king' => 0
    );
    const SIDE_VALUES = array(
        'white' => 1,
        'black' => -1
    );

    function __construct($color, $square_string, $type) {
        if ( in_array($color, self::VALID_COLORS) ) {
            $this->color = $color;
        } else {
            throw new Exception('ChessPiece Class - Invalid Color');
        }

        $this->square = new ChessSquare($square_string);

        if ( in_array($type, self::VALID_TYPES) ) {
            $this->type = $type;
        } else {
            throw new Exception('ChessPiece Class - Invalid Type');
        }

        $this->value = self::PIECE_VALUES[$type] * self::SIDE_VALUES[$color];
    }

    function __clone() {
        $this->square = clone $this->square;
    }

    function get_unicode_symbol()
    {
        $dictionary_key = $this->color . '_' . $this->type;

        return self::UNICODE_CHESS_PIECES[$dictionary_key];
    }

    function get_fen_symbol()
    {
        $dictionary_key = $this->color . '_' . $this->type;

        return self::FEN_CHESS_PIECES[$dictionary_key];
    }

    function on_rank($rank)
    {
        if ( $rank == $this->square->rank ) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
}

ChessSquare.php

<?php

class ChessSquare {
    const VALID_SQUARES = array(
        'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8',
        'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8',
        'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8',
        'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8',
        'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8',
        'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8',
        'g1', 'g2', 'g3', 'g4', 'g5', 'g6', 'g7', 'g8',
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8'
    );

    var $rank;
    var $file;
    var $alphanumeric;

    function __construct($alphanumeric) {
        $file_letters = new Dictionary(array(
            1 => 'a',
            2 => 'b',
            3 => 'c',
            4 => 'd',
            5 => 'e',
            6 => 'f',
            7 => 'g',
            8 => 'h'
        ));

        if ( in_array($alphanumeric, self::VALID_SQUARES) ) {
            $this->alphanumeric = $alphanumeric;

            $this->file = $file_letters->check_dictionary(substr($alphanumeric, 0, 1));
            $this->rank = substr($alphanumeric, 1, 1);
        } else {
            throw new Exception("ChessSquare Class - Invalid Square - $alphanumeric = " . var_export($alphanumeric, TRUE));
        }
    }
}

Dictionary.php

<?php

// Array of pairs. The list of both columns put together cannot contain duplicates.
class Dictionary {
    var $array_of_pairs;

    function __construct($array) {
        // make sure there are no duplicates

        $this->array_of_pairs = $array;
    }

    function check_dictionary($search_key) {
        if ( isset($this->array_of_pairs[$search_key]) ) {
            return $this->array_of_pairs[$search_key];
        } elseif ( $search_results = array_search($search_key, $this->array_of_pairs) ) {
            return $search_results;
        } else {
            return NULL;
        }
    }
}

script.js

$(document).ready(function(){
    $('select').dblclick(function(){
        $('#make_move').submit();
    });

    $('.draggable_piece').on("dragstart", function (event) {
        var dt = event.originalEvent.dataTransfer;
        dt.setData('Text', $(this).closest('td').attr('id'));
    });

    $('table td').on("dragenter dragover drop", function (event) {  
        event.preventDefault();

        if (event.type === 'drop') {
            var oldsquare = event.originalEvent.dataTransfer.getData('Text',$(this).attr('id'));

            var newsquare = $(this).attr('id');

            var coordinate_notation = oldsquare + newsquare;

            var option_tag_in_select_tag = $("select[name='move'] option[data-coordinate-notation='" + coordinate_notation + "']");

            if ( option_tag_in_select_tag.length != 0 ) {
                option_tag_in_select_tag.attr('selected','selected');

                $('#make_move').submit();
            }
        };
    });
})

style.css

body {
    font-family:sans-serif;
}

input[name="fen"] {
    width: 500px;
}

input[type="submit"],
input[type="button"] {
    font-size: 12pt;
}

textarea[name="pgn"] {
    width: 500px;
    font-family: sans-serif;
}

select[name="move"] {
    width: 8em;
}

.two_columns {
    display: flex;
    width: 600px;
}

.two_columns>div:nth-child(1) {
    flex: 60%;
}

.two_columns>div:nth-child(2) {
    flex: 40%;
}

#graphical_board {
    table-layout: fixed;
    border-collapse: collapse;
}

#graphical_board td {
    height: 40px;
    width: 40px;
    padding: 0;
    margin: 0;
    text-align: center;
    vertical-align: middle;
    font-size: 30px;
    font-weight: bold;
    font-family: "Arial Unicode MS", "Lucida Console", Courier, monospace;
    cursor: move;
}

.black {
    background-color: #769656;
}

.white {
    background-color: #EEEED2;
}

.status_box {
    background-color: #F0F0F0;
    border: 1px solid black;
    padding-top: 2px;
    padding-bottom: 2px;
    padding-left: 4px;
    padding-right: 4px;
    width: 310px;
    margin-bottom: 5px;
}

view.php

<!DOCTYPE html>

<html lang="en-us">
    <head>
        <meta charset="utf-8" />

        <title>
            AdmiralAdama Chess
        </title>

        <link rel="stylesheet" href="style.css">

        https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js

        http://scripts.js
    </head>

    <body>
        <h1>
        AdmiralAdama Chess
        </h1>

        
<table id="graphical_board"> <tbody> <?php foreach ( $graphical_board_array as $rank => $row ): ?> <tr> <?php foreach ( $row as $file => $column ): ?> <td id ="<?php echo $column['id']; ?>" class="<?php echo $column['square_color']; ?>" > <span class="draggable_piece" draggable="true" > <?php echo $column['piece']; ?> </span> </td> <?php endforeach; ?> </tr> <?php endforeach; ?> </tbody> </table> <!-- <input type="submit" name="flip" value="Flip The Board" /> --> <input type="button" onclick="window.location='.'" value="Reset The Board" /> </div>
Legal Moves:
$move ): ?> board->fen; ?>" data-coordinate-notation="coordinate_notation; ?>" > notation; ?>
Move Count:
Load Time: ms
</div> <form id="import_fen"> <p> FEN:<br /> <input type="text" name="fen" value="<?php echo $fen; ?>" /><br /> <input type="submit" value="Import FEN" /> </p> </form> </body> </html>


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.