Skip to main content
Notice removed Draw attention by RedDragonWebDesign
Bounty Ended with Sᴀᴍ Onᴇᴌᴀ's answer chosen by RedDragonWebDesign
emphasizing that I want reviewers to focus on the PHP code, in particular refactoring and performance
Source Link

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

  • PHP Refactoring - Make the PHP 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 practicesPHP Performance - Speed up the PHP 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 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.

<?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();
        
        // TODO: 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;
            
            // TODO: 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);
                }
            }
            
            // TODO: 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;
    }}

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.

<?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;
    }}

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

  • PHP Refactoring - Make the PHP code more readable / more organized / use classes better.
  • PHP Performance - Speed up the PHP 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.)
<?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();
        
        // TODO: 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;
            
            // TODO: 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);
                }
            }
            
            // TODO: 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;
    }}
edited tags
Link
Tweeted twitter.com/StackCodeReview/status/1039303020201345025
Notice added Draw attention by RedDragonWebDesign
Bounty Started worth 50 reputation by RedDragonWebDesign
Source Link
Loading